CODE | DEMO
UPDATE: This is the third of a three part series on CodeIgniter, Redis, and Socket.IO integration. Please be sure to read the other posts as well.
Part 1: You are here.
Part 2: Use Redis instead of MySQL for CodeIgniter Session Data
Part 3: Live Updates in CodeIgniter with Socket.IO and Redis
I was assigned to work on a PHP project a few weeks back that utilized the CodeIgniter framework. I’ve used MVC frameworks in the past in other languages, and because CodeIgniter does not deviate too far from common MVC patterns, it was pretty easy to pick up. To get myself up to speed, I put together a sample project based of what I learned from the documentation and a few tutorials. It’s a small microblogging app with very basic user auth and CRUD functionality. The basics of it go like this…
- User login screen with basic password authentication.
- Each user is assigned a ‘team’ number. Users can only view posts from their teammates.
- An admin can view all posts from all users.
- Each user has a ‘tagline’.
- The tagline can be edited by clicking on it and typing something new (modern browsers only, as it uses ‘contenteditable’)
- Hovering over a user’s avatar reveals the username and tagline for that user.
- Admins can create new user accounts.
- A user’s own messages appear below their profile. This is limited by 5 posts, currently.
- Careful, there is not much form validation going on at the moment.
Building it gave me a good feel for the basic mechanics of CodeIgniter and allowed me to brush up on some PHP. The real motivation behind this project, however, is to eventually work in a Socket.IO implementation to allow real time updates for the user. I’ve been eyeing NodeJS for quite some time now, but never really had cause to use it. Fortunately, the project I was working on needed a more robust and scalable ‘real time’ framework to replace a rudimentary long-polling system. Socket.IO would have been a pretty good solution, in my opinion. Unfortunately, the project was cancelled before I could get started. But since I’ve already got the ball rolling on this sample application, I figure I might as well finish, and learn a few things in case a situation like this arises again. You never know…
The Setup
The front end of the application uses Twitter Bootstrap for styling and layout, jQuery for client-side interactivity, PHP and CodeIgniter for most of the functionality, and MySQL for data storage. This is a fairly common toolset that runs on most L/W/M/AMP-style environment stacks. The code available on GitHub has everything you need to run the app yourself, provided you have a web server, MySQL, and PHP 5.3 or greater.
You’ll need to create a database (preferably called ‘cisock’), and then import cisock.sql in the root of the ‘partOne’ folder in the repository. You can do this with the following command from the command line (be sure to change ‘yourusername’ and ‘/path/to/’ to match your local setup):
Once you’ve gotten the code checked out into your web root and the SQL imported, you’ll need to do some slight configuration before getting started. In ‘part_one/application/config/config.php’ you will need to change line 17 to reflect your local URL. If you simply cloned the project directly into your ‘localhost’ web root, then no changes will likely be needed. A similar fix is necessary in ‘part_one/assets/js/main.js’ line 6. The context root of your application goes here. Again, if you cloned to your web root, the default value should be fine. Ideally, the context root should only have to be configured in one place, but it is what it is for now.
Secondly, the settings in ‘part_one/application/config/database.php’ must be set to reflect your local database configuration. The following entries are specific to your local environment:
$db['default']['username'] = 'yourdatabaseusernamehere';
$db['default']['password'] = 'yourdatabasepasswordhere';
$db['default']['database'] = 'cisock'; //or use the database name you chose, if it is different
Once that is done, you should be able to navigate your browser to the /part_one/ directory and see the login screen.
To recap:
- Clone the repo
- Set up the database and import the tables from cisock.sql
- Edit database.php and also config.php and main.js if necessary
The Code
I tried to follow suggested CodeIgniter conventions as closely as possible. As such, the file and directory structure is pretty much as it is out of the box. I altered /application/config/routes.php to set the login controller as the entry point for the application like so:
The index method in /application/controllers/login.php checks to see if the user is logged in, and if not will redirect to the login screen by calling the show_login function. The code immediately below is login/index – which will run when application’s url is loaded in the browser.
if( $this->session->userdata('isLoggedIn') ) {
redirect('/main/show_main');
} else {
$this->show_login(false);
}
}
Because the ‘isLoggedIn’ session variable starts out as false, the show_login() function is called. The ‘false’ argument indicates that an error message is not to be shown.
$data['error'] = $show_error;
$this->load->helper('form');
$this->load->view('login',$data);
}
That last line there: $this->load->view('login',$data);
is what opens up the login view ( /application/views/login.php ). When the user types in credentials and clicks the ‘sign-in’ button, the login_user function is called through a normal form POST as indicated by this line of code in /application/views/login.php: echo form_open('login/login_user') ?>
. The form_open function is part of CodeIgniter’s Form Helper, which generates the form’s HTML for you.
The login/login_user function gives us our first taste of a CodeIgniter model. It loads up an instance of a user model and calls the validate_user method on that model, passing it the email and password typed in by the user. Take a look at the code below for the entire sequence.
// Create an instance of the user model
$this->load->model('user_m');
// Grab the email and password from the form POST
$email = $this->input->post('email');
$pass = $this->input->post('password');
//Ensure values exist for email and pass, and validate the user's credentials
if( $email && $pass && $this->user_m->validate_user($email,$pass)) {
// If the user is valid, redirect to the main view
redirect('/main/show_main');
} else {
// Otherwise show the login screen with an error message.
$this->show_login(true);
}
}
In the ‘user’ model, a couple of interesting things happen. First, a query is built using CodeIgniter’s ActiveRecord implementation. The username and password entered by the user are compared to the user table in the database to see if the credentials exist. If so, the corresponding record in the database will be retrieved. If that happens, the data retrieved from the database will be used to set session variables using the set_session function of CodeIgniter’s Session class. All the code for this is in /application/model/user_m.php and can be seen below.
function validate_user( $email, $password ) {
// Build a query to retrieve the user's details
// based on the received username and password
$this->db->from('user');
$this->db->where('email',$email );
$this->db->where( 'password', sha1($password) );
$login = $this->db->get()->result();
// The results of the query are stored in $login.
// If a value exists, then the user account exists and is validated
if ( is_array($login) && count($login) == 1 ) {
// Set the users details into the $details property of this class
$this->details = $login[0];
// Call set_session to set the user's session vars via CodeIgniter
$this->set_session();
return true;
}
return false;
}
function set_session() {
// session->set_userdata is a CodeIgniter function that
// stores data in a cookie in the user's browser. Some of the values are built in
// to CodeIgniter, others are added (like the IP address). See CodeIgniter's documentation for details.
$this->session->set_userdata( array(
'id'=>$this->details->id,
'name'=> $this->details->firstName . ' ' . $this->details->lastName,
'email'=>$this->details->email,
'avatar'=>$this->details->avatar,
'tagline'=>$this->details->tagline,
'isAdmin'=>$this->details->isAdmin,
'teamId'=>$this->details->teamId,
'isLoggedIn'=>true
)
);
}
So now that the user is authenticated and their session info is set in a cookie, CodeIgniter will take an extra step and store the user’s IP address, session ID, user agent string and last activity timestamp in the database. So when the user logs out, closes the browser, or is idle for too long, the session will expire and be cleared from the database. If someone with a cookie containing the same session ID then tries to connect to the application, it will be invalid because it won’t match any of the sessions stored in the database. Check out the Session class documentation for a more thorough explanation.
So now that the user is logged in, the ‘main’ controller can do its thing. The show_main function runs just before loading the main view, and does a number of things to prepare for displaying the view to the user. The user’s details are used to change certain parts of the view, such as which posts to display (team only, or everyone) and the admin controls.
The show_main function grabs some data from the user’s session, retrieves all the user’s posted messages, counts the total number of messages posted by the user, and retrieves the messages from other users. All of this info is placed into the $data object and passed to the ‘main’ view (/application/views/main.php). Much of the heavy lifting is taken care of by ActiveRecord commands in the Post model (/application/model/post_m).
That’s about it, as far as logging in goes. Now that everything is set up using conventional CodeIgniter practices, I can begin the process of converting the server side session data to be stored in Redis, rather than in a MySQL table…
UPDATE:
See Part 2: Use Redis instead of MySQL for CodeIgniter Session Data
and Part 3: Live Updates in CodeIgniter with Socket.IO and Redis