Codeigniter DoctrineIn this episode we are going to build a User Login system following these steps:

- We will build a Login Form View and a Login Controller.
- Learn about some URL Helpers.
- Change the Default Controller.
- Implement User Authentication.
- Learn about Singleton Pattern and use it to improve our code design.
- Learn about the Sessions Library.
- Implement User Logout.

ci_doctrine_day4_1

"CodeIgniter and Doctrine from Scratch" Series:

download_code

Login Form View

First thing we are going to do is to create the View for the Login Form.

We are going to make this one very similar to the Signup Form we created in the last article.

  • Create: system/application/views/login_form.php
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Login Form</title>
	<link rel="stylesheet" href="<?php echo base_url(); ?>css/style.css"
		type="text/css" media="all">
</head>
<body>

<div id="signup_form">

	<p class="heading">User Login</p>

	<?php echo form_open('login/submit'); ?>

	<?php echo validation_errors('<p class="error">','</p>'); ?>

	<p>
		<label for="username">Username: </label>
		<?php echo form_input('username',set_value('username')); ?>
	</p>
	<p>
		<label for="password">Password: </label>
		<?php echo form_password('password'); ?>
	</p>
	<p>
		<?php echo form_submit('submit','Login'); ?>
	</p>

	<?php echo form_close(); ?>
	<p>
		<?php echo anchor('signup','Create an Account'); ?>
	</p>

</div>

</body>
</html>

All I did was copy the contents of the signup form, change the page title, remove some form fields and added a link at the bottom to the signup form.

anchor()

On Line 33 in the file above, I used a function named anchor(). This is part of the URL Helper in CodeIgniter. It helps us create links with ease. The first argument is the Controller Name, and the second argument is the Link Text. You can also pass an optional third argument for extra html attributes. The result is:

<a href="http://localhost/ci_doctrine/signup">Create an Account</a>

Style the links

I am just going to make a small addition to the stylesheet so our links look better.

  • Edit: css/style.css
body {
	font-family: "Trebuchet MS",Arial;
	font-size: 14px;
	background-color: #212426;
	color: #11151E;
}

a {
	color: #FFF;
}

a:hover {
	color: #B9AA81;
}

/* ... */

I only added the highlighted lines.

Link from the Signup Form

Likewise, we will add a link from the Signup Form to the new Login Form.

  • Edit: system/application/views/signup_form.php
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Signup Form</title>
	<link rel="stylesheet" href="<?php echo base_url(); ?>css/style.css"
		type="text/css" media="all">
</head>
<body>

<div id="signup_form">

	<p class="heading">New User Signup</p>

	<?php echo form_open('signup/submit'); ?>

	<?php echo validation_errors('<p class="error">','</p>'); ?>

	<p>
		<label for="username">Username: </label>
		<?php echo form_input('username',set_value('username')); ?>
	</p>
	<p>
		<label for="password">Password: </label>
		<?php echo form_password('password'); ?>
	</p>
	<p>
		<label for="passconf">Confirm Password: </label>
		<?php echo form_password('passconf'); ?>
	</p>
	<p>
		<label for="email">E-mail: </label>
		<?php echo form_input('email',set_value('email')); ?>
	</p>
	<p>
		<?php echo form_submit('submit','Create my account'); ?>
	</p>
	<?php echo form_close(); ?>
	<p>
		<?php echo anchor('login','Login Form'); ?>
	</p>

</div>

</body>
</html>

I only added the highlighted lines.

Note: We just linked to a Controller named login. This Controller does not exist yet. So let’s create it.

Login Controller

First we are going create a sort of skeleton structure for our new Controller.

  • Create: system/application/controllers/login.php
<?php
class Login extends Controller {

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

		$this->load->helper(array('form','url'));
		$this->load->library('form_validation');
	}

	public function index() {
		$this->load->view('login_form');
	}

	public function submit() {

		if ($this->_submit_validate() === FALSE) {
			$this->index();
			return;
		}

		redirect('/');

	}

	private function _submit_validate() {

	}

}

This looks very similar to the submit Controller we created in the last article. The only new thing is the highlighted line.

redirect()

This function is part of the URL Helper. It forwards the surfer to a specified Controller. In this case we only passed ‘/’, which will basically send the surfer to the Home Page or in other words the Default Controller.

So the user will end up at http://localhost/ci_doctrine/.

Test The Login Controller+View

ci_doctrine_day4_4

  • Now click the link that says Create an Account.

You should see our old Signup Form page.

ci_doctrine_day3_3

Works great!

Default Controller

When someone visits the main page of our website (i.e. there is no controller name in the url), it calls the Default Controller. Right now if you go to http://localhost/ci_doctrine/, you will see this:

ci_doctrine_day1_3

Because CodeIgniter is loading a Controller named welcome by default.

Let’s say we would like to have a Default Controller named home instead.

  • Create: system/application/controllers/home.php
<?php
class Home extends Controller {

	public function index() {
		echo "Home Sweet Home!";
	}	

}

Routes File

  • Edit: system/application/config/routes.php

Changing just one line:

// ...
$route['default_controller'] = "home";
$route['scaffolding_trigger'] = "";
// ...

That’s it. Now when you go to http://localhost/ci_doctrine/, you will see our controller:

Home Sweet Home!

The Routes File has some other purposes, which we will not get into right now. But you can read more about it here.

User Login (Authentication)

Now that our forms are in place, we need to add authentication functionality to our application.

First I want to show you how to accomplish it in a simple and most common way. Once we cover that, I will show you a better way of handling this.

Authentication with Form Validation

  • Edit: system/application/controllers/login.php
<?php
class Login extends Controller {

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

		$this->load->helper(array('form','url'));
		$this->load->library('form_validation');
	}

	public function index() {
		$this->load->view('login_form');
	}

	public function submit() {

		if ($this->_submit_validate() === FALSE) {
			$this->index();
			return;
		}

		redirect('/');

	}

	private function _submit_validate() {

		$this->form_validation->set_rules('username', 'Username',
			'trim|required|callback_authenticate');

		$this->form_validation->set_rules('password', 'Password',
			'trim|required');

		$this->form_validation->set_message('authenticate','Invalid login. Please try again.');

		return $this->form_validation->run();

	}

	public function authenticate() {

		// get User object by username
		if ($u = Doctrine::getTable('User')->findOneByUsername($this->input->post('username'))) {

			// this mutates (encrypts) the input password
			$u_input = new User();
			$u_input->password = $this->input->post('password');

			// password match (comparing encrypted passwords)
			if ($u->password == $u_input->password) {
				unset($u_input);
				return TRUE;
			}
			unset($u_input);
		}

		return FALSE;
	}
}

Highlighted functions have been updated.

First I added some form validation rules inside the _submit_validate() function, like in the last article. But this time I added a Callback rule. Notice on Line 29: callback_authenticate. The format is callback_[function_name].

On Line 34 I set a custom message for this validation rule, in case it fails.

Then I added the function named authenticate. This callback function needs to return TRUE when everything is good, and FALSE when the validation fails.

Lines 43-53: This is where I perform the username and password check for the login. First I attempt to get the User Record searching by the input username. (We learned about the getTable and findOneBy* functions in the last article).

Applying the Mutator to an external value

Remember we added a Mutator to the password field in the last article?

Since the stored passwords are encrypted, I need to apply the same encryption to the user input, before I can compare the two.

That’s why I create a new User object named $u_input. on Line 46 and assigned the input password to it. This is how I trigger the Mutator function on the password that was entered in the form. (If anyone knows a better of doing this, let me know!)

Now the encrypted version of the input password resides in $u_input->password. So I was able to compare it to $u->password.

I also unset the $u_input object, just to make sure it doesn’t get saved as a new record by accident.

Testing the Form

First create a new user by going to the Signup Form at http://localhost/ci_doctrine/signup.

Now go to: http://localhost/ci_doctrine/login and use a wrong login. You should see:

ci_doctrine_day4_1

When you use a correct login, you should get forwarded back to the home page.

Home Sweet Home!

Our Login Form works!

Now the Better Way!

I mentioned I was going to change things a bit and show you how to do this in a better way.

What’s wrong with the current method?

  1. I don’t like how the authentication code is all inside the Controller. This is not good for code reusability.
  2. It should be part of the User Model, since it’s directly related to it.
  3. We stored the User Record in a variable named $u. But it’s inside a function and not global. So, if other Controllers need to use it, they can’t.
  4. Turning the object $u directly into a Global Variable would be a bad design decision.
  5. We do not want to store the whole user object inside the session. Doctrine Models have a lot of internal data, which could quickly inflate the size of our session storage. Only thing we are going store in the session is the user_id.

Singleton Pattern to the rescue!

Some of you may not know what Singleton Pattern means. It’s one of the most popular design patterns, and I will show you how we are going to use it.

With this new class I am going to create, we will have a better design for our authentication functionality, and we will also make the User Model instance for the logged in user globally available in our application.

Singleton Pattern for Current User

First let’s build the skeleton:

  • Create: system/application/models/current_user.php
<?php
class Current_User {

	private static $user;

	private function __construct() {}

	public static function user() {

		if(!isset(self::$user)) {

			// put the User record into $this->user
			// ...

		}

		return self::$user;
	}

	public function __clone() {
		trigger_error('Clone is not allowed.', E_USER_ERROR);
	}

}

Now let’s go over the code:

  • There is a reason I called the class Current_User. Because this static class will always return an instance of the User class, for the current logged in user.
  • This class does NOT extend Doctrine_Record, because we are not really creating a Doctrine Model.
  • The $user variable will contain the User object for the logged in user. And we will access it by Current_User::user(). That is the syntax for calling a static class function.
  • We will never create an instance of this Current_User class, so the constructor is set to private.
  • We also disallow cloning of this class so the __clone() function triggers an error.

Now I’m going to add a bit more code to make this functional.

  • Edit: system/application/models/current_user.php
<?php
class Current_User {

	private static $user;

	private function __construct() {}

	public static function user() {

		if(!isset(self::$user)) {

			$CI =& get_instance();
			$CI->load->library('session');

			if (!$user_id = $CI->session->userdata('user_id')) {
				return FALSE;
			}

			if (!$u = Doctrine::getTable('User')->find($user_id)) {
				return FALSE;
			}

			self::$user = $u;
		}

		return self::$user;
	}

	public static function login($username, $password) {

		// get User object by username
		if ($u = Doctrine::getTable('User')->findOneByUsername($username)) {

			// this mutates (encrypts) the input password
			$u_input = new User();
			$u_input->password = $password;

			// password match (comparing encrypted passwords)
			if ($u->password == $u_input->password) {
				unset($u_input);

				$CI =& get_instance();
				$CI->load->library('session');
				$CI->session->set_userdata('user_id',$u->id);
				self::$user = $u;

				return TRUE;
			}

			unset($u_input);
		}

		// login failed
		return FALSE;

	}

	public function __clone() {
		trigger_error('Clone is not allowed.', E_USER_ERROR);
	}

}

Let’s go over the code in user() method first:

  • The usage for this function is: Current_User::user() and it returns the User object for the logged in user.
  • Line 10: We check to see if the user object is already there. If it is, we return it at line 26 .
  • Lines 12-17: We load the session library, so we can read/write in the session. If the user is logged in, his id should be stored there. If we don’t see the user_id, it returns FALSE.
  • We had to use the $CI object to load the session library because this is a static class.
  • Lines 19-21: Fetch the User object by id. If we can’t, return FALSE. Note that we used a function named find() to look up by id.
  • Lines 23 and 26: Everything went well, so we store the user object in the static variable $user and return it.

Now, when we call Current_User::user() , we can get the User object as if it’s a global variable. And if it returns false, it means the user is NOT logged in.

Let’s go over the login() method:

  • I copied most of the code from the login Controller we created earlier. The code is from the authenticate() method.
  • I added loading of the session library at line 42-43, so I could store the user_id in the session at line 44
  • I also added line 45. Once the user gets logged in, the $user static variable is set, so it can be returned by the user() function later.

By the way, we just learned how to use Sessions in CodeIgniter. It’s not very complicated. All you need to do is to load the library and you can use the userdata() and set_userdata() function to read and write the variables. You can read more about it here.

Now we can call the static function like this: Current_User::login($username,$password) , and it will take care of the rest.

Phew! I hope that was not very complicated. But I’m sure you will soon understand why we went through all of that.

Simplified Login Controller

We are about the see the first benefit of creating all that code above.

  • Edit: system/application/controllers/login.php

Only editing the authenticate() function:

<?php
class Login extends Controller {

	// ...

	public function authenticate() {

		return Current_User::login($this->input->post('username'),
									$this->input->post('password'));

	}
}

See how easy that is now?

When we call Current_User::login() it attempts to login the user, and returns TRUE on success. If the login information is incorrect, it returns FALSE.

Once the user is logged in this way, we can retrieve the User object for the logged in user, anywhere in our application by simply calling Current_User::user().

Also note that we didn’t need to put “$this->load->model(‘Current_User’)” in the code, and we will never have to. The Current_User class will always be globally available in the application, because of the Doctrine autoloader that was registered in the “system/application/plugins/doctrine_pi.php” file.

Logout

Now we are going to let users logout by clicking a link. But first:

Autoloading Libraries

It seems like we are going to be using the Session Library almost everywhere in our application. Let’s have it autoloaded by CodeIgniter so we don’t have to keep calling $this->library->load(‘session’) all the time.

  • Edit: system/application/config/autoload.php

Find this line:

// ...
$autoload['libraries'] = array('session');
// ...

Let’s also autoload the URL Helper.

  • Edit: system/application/config/autoload.php

Find this line:

// ...
$autoload['helper'] = array('url');
// ...

Now that’s out of the way.

Logout Controller

  • Create: system/application/controllers/logout.php
<?php
class Logout extends Controller {

	public function index() {

		$this->session->sess_destroy();
		$this->load->view('logout');
	}

}

We remove all of the session data by calling $this->session->sess_destroy(). Now the user will no longer be logged in.

Logout View

  • Create: system/application/views/logout.php
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Logout</title>
	<link rel="stylesheet" href="<?php echo base_url(); ?>css/style.css"
		type="text/css" media="all">
	<meta http-equiv="refresh" content="3;url=<?php echo base_url(); ?>">
</head>
<body>

<div>

	<p>
		You are now logged out.
	</p>
	<p>
		Redirecting you <?php echo anchor('/','home'); ?>.
	</p>

</div>

</body>
</html>

This logout page will display a short message and redirect the surfer using a meta refresh.

Style Changes

I have made some css changes, so just copy paste the following:

  • Edit: css/style.css
body {
	font-family: "Trebuchet MS",Arial;
	font-size: 14px;
	background-color: #212426;
	color: #B9AA81;
}

a {
	color: #FFF;
}

a:hover {
	color: #B9AA81;
}

input, textarea, select {
	font-family:inherit;
	font-size:inherit;
	font-weight:inherit;
}

#signup_form {
	margin-left: auto;
	margin-right: auto;
	width: 360px;

	font-size: 16px;

}

#signup_form .heading {
	text-align: center;
	font-size: 22px;
	font-weight: bold;
	color: #B9AA81;
}

#signup_form form {

	background-color: #B9AA81;
	padding: 10px;

	border-radius: 8px;
	-moz-border-radius: 8px;
	-webkit-border-radius: 8px;

}

#signup_form form label {
	font-weight: bold;
	color: #11151E;
}

#signup_form form input[type=text],input[type=password] {
	width: 316px;
	font-weight: bold;
	padding: 8px;

	border: 1px solid #FFF;
	border-radius: 4px;
	-moz-border-radius: 4px;
	-webkit-border-radius: 4px;
}

#signup_form form input[type=submit] {
	display: block;
	margin: auto;
	width: 200px;
	font-size: 18px;
	background-color: #FFF;
	border: 1px solid #BBB;

}

#signup_form form input[type=submit]:hover {
	border-color: #000;
}

#signup_form .error {
	font-size: 13px;
	color: #690C07;
	font-style: italic;
}

Home View

Now we are going to see how easy it is to fetch user information for the logged in user.

  • Create: system/application/views/home.php
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Home</title>
	<link rel="stylesheet" href="<?php echo base_url(); ?>css/style.css"
		type="text/css" media="all">
</head>
<body>

<div>

	<?php if(Current_User::user()): ?>
		<h2>Hello <em><?php echo Current_User::user()->username; ?></em>.</h2>
		<h2><?php echo anchor('logout','Logout'); ?></h2>
	<?php else: ?>
		<h2>New Users: <?php echo anchor('signup','Create an Account'); ?>.</h2>
		<h2>Members: <?php echo anchor('login','Login'); ?>.</h2>
	<?php endif; ?>

</div>

</body>
</html>

Home Controller

  • Edit: system/application/controllers/home.php
<?php
class Home extends Controller {

	public function index() {
		$this->load->view('home');
	}	

}

That’s it!

Let’s Test It

First you will see:

ci_doctrine_day4_2

And it will forward you home:

ci_doctrine_day4_3

  • Click the Login link to be forwarded to our Login Form.

ci_doctrine_day4_4

  • Enter a correct login info and you will be redirected home again.

Now you should see:

ci_doctrine_day4_5

In this case my username is burak, and it was displayed back to me.

Voila!

Stay Tuned

We covered quite a few new concepts in today’s tutorial. I hope you enjoyed it and learned from it.

In the next tutorial we will explore the CRUD (Create, Read, Update and Delete) functionality with Doctrine in more detail.

See you next time!

"CodeIgniter and Doctrine from Scratch" Series: