Login and Registration with Object Oriented Php and PDO

Coding \ Php Sunny Comments May 17th 2013
In this tutorial I will demonstrate Object oriented Php (OOP) and Php Data Objects (PDO) that many Php developers still have difficulty understanding and making a shift from procedural programming and the now deprecated mysql_x functions. I will not explain the basics or the benefits of them, but rather explain how you can use OOP and PDO to create a full fledged Login and Registration system.

Download Source Code
This is Part 1 of the 2 part series. You can continue to Part 2, once you get done with this.

Although I will try my best to explain core principles and features of OOP and PDO as we go along, if you do not know anything about OOP and PDO, I suggest you read these tutorials:

Functionality

In this first part, what we are going to create is actually very simple. First we will let the user fill out a registration form. Then if the user has supplied valid information, that information will be stored in our database and an email would be send to the supplied email address with a link to activate the account. After which the user can login through a login form. If the user supplies valid username/password and if he has activated the account by clicking on the link which we had sent, he will be logged in and directed to the user's personalized home page.

Without further ado, lets get started!

Creating our Database and users table

Before we begin, make sure you have an environment setup where you can use Php and MySQL. I'll be using Xampp which is a great package. Also I'll be using phpmyAdmin as my tool for managing database, but you are free to choose your own.

Let's create a database named 'lar' for log in and registration, however, you can choose any name you want. Also, let's create a table named 'users' in that database.

Following is the image of that table that I have, also showing the database name.

database.jpg

You can see all the fields, their data types and lengths in the picture. For example, the field 'username' has the data type varchar and length 18. Also make sure that the field 'id' is autoincrement and has the index set to primary.

Folder structure and required files

Lets create the folder structure and the files (empty for now) that we are going to need, so we get an overview of what all is required.

folder_structure.jpg

Connecting to the database using PDO

Lets open our database.php file and make a connection to our database using PDO.

database.php
<?php 
# We are storing the information in this config array that will be required to connect to the database.
$config = array(
	'host'		=> 'localhost',
	'username'	=> 'root',
	'password'	=> '',
	'dbname' 	=> 'lar'
);
#connecting to the database by supplying required parameters
$db = new PDO('mysql:host=' . $config['host'] . ';dbname=' . $config['dbname'], $config['username'], $config['password']);

#Setting the error mode of our db object, which is very important for debugging.
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
?>

So above we have chosen mysql as our database and supplied the required information which was:

  • host - This is almost always localhost whether you are developing locally or when your site is on a web host. Localhost just means the host where the current file is located.
  • username - Username for my database is root
  • password - I have not set a password for my root user.
  • dbname - We named our database 'lar'.

Now we have created an object called $db. This object is basically a reference to our PDO class that we have instantiated by supplying our parameters. Remember PDO is a built-in class and it contains methods(which just means functions inside a class) that we are going to use to query our database.

Creating our classes in users.php and general.php

The users.php file will contain all the methods that are related to users on our website, such as registering the users, logging the user in and retrieving information about the registered user.

In a similar way general.php will contain general functions for our site.

users.php
<?php 
class Users{

	private $db;

	public function __construct($database) {
	    $this->db = $database;
	}

}
general.php
<?php 
class General{


}

We have just created our classes. We will add functions in them as we go along in the tutorial.

Since functions inside a class cannot use variables that are defined outside of it, we have used the __construct() method in our Users class to make the $db variable(or object) available in our class. This will make more sense when we will pass in the $db object that we created in our database.php in the Users class when we instantiate it.

Note: You can also make variables outside of classes available to the methods inside by making the variable global. But we are not going to cover that here, since it is not required.

Creating our init.php file

The init.php file will be included in all the files that are in our root directory which are index.php, register.php, login.php and so on, that are basically the pages that the user will access with his browser.

Below we start the user's session, include the files we just worked on and instantiate the two classes. We have also created an errors array that will help us in error handling later in the tutorial.

init.php
<?php 
#starting the users session
session_start();
require 'connect/database.php';
require 'classes/users.php';
require 'classes/general.php';

$users 		= new Users($db);
$general 	= new General();

$errors 	= array();

Lets take a pause and examine what we have so far. Here we have included all the neccessary files. When we include this init.php in our files in the root directory, those files will have 3 objects already created in them, which are $general, $users and $db. We created $general and $users in this file by instantiating General class and Users class that are present in general.php and users.php; whereas, $db was already created in our database.php

Now we will be able to use all the functions that we will create in our General and Users class and the built-in functions in the PDO class in all our pages.

Creating basic styling rules.

We'll start creating our pages, but first lets make some styling rules that will apply to all of them.

This is our style.css file. Just some basic style mostly to center the content, nothing pretty.
@charset "utf-8";

body{
	font: 0.9em Tahoma, Verdana, Arial;
	line-height:160%;
	background: #ddd;
}

#container{
	width: 1000px;
	padding: 25px;
	background: #fff;
	margin: 100px auto 0 auto;
}

h4{
	margin: 0;
}

ul{
	padding: 0;
}

ul li{
	display: inline-block;
	padding: 15px;
	list-style: none;
}

Our index page.

For the sake of this tutorial I have only put a welcome message and navigation that contains links to the index.php, login.php and register.php. The rest of the pages will use the same basic markup.

index.php
<?php 
#including our init.php
require 'core/init.php';
?>
<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<link rel="stylesheet" type="text/css" href="css/style.css" >
	<title>Login and registration</title>
</head>
<body>	
	<div id="container">
		<ul>
			<li><a href="index.php">Home</a></li>
			<li><a href="register.php">Register</a></li>
			<li><a href="login.php">Login</a></li>
		</ul>
		<h1>Welcome to our site!</h1>
	</div>
</body>
</html>

Make sure you include the init.php file in all the pages. We have done it with the require function.

With the style sheet included, your index.php file should look like this:

index.jpg

Now as we go along, we are going to create our pages that the user is going to view and add functions in our classes accordingly.

Registering the user.

Following is the register.php file that contains a form, that when submitted sends data with the method of POST to register.php itself. At the top of the file is all the logic that shows what happens when $_POST is set, which means when the form is submitted. The logic includes verifying the data, checking for errors and registering the users in case of no errors.

register.php
<?php 
require 'core/init.php';

# if form is submitted
if (isset($_POST['submit'])) {

	if(empty($_POST['username']) || empty($_POST['password']) || empty($_POST['email'])){

		$errors[] = 'All fields are required.';

	}else{
        
        #validating user's input with functions that we will create next
        if ($users->user_exists($_POST['username']) === true) {
            $errors[] = 'That username already exists';
        }
        if(!ctype_alnum($_POST['username'])){
            $errors[] = 'Please enter a username with only alphabets and numbers';	
        }
        if (strlen($_POST['password']) <6){
            $errors[] = 'Your password must be at least 6 characters';
        } else if (strlen($_POST['password']) >18){
            $errors[] = 'Your password cannot be more than 18 characters long';
        }
        if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) === false) {
            $errors[] = 'Please enter a valid email address';
        }else if ($users->email_exists($_POST['email']) === true) {
            $errors[] = 'That email already exists.';
        }
	}

	if(empty($errors) === true){
		
		$username 	= htmlentities($_POST['username']);
		$password 	= $_POST['password'];
		$email 		= htmlentities($_POST['email']);

		$users->register($username, $password, $email);// Calling the register function, which we will create soon.
		header('Location: register.php?success');
		exit();
	}
}

if (isset($_GET['success']) && empty($_GET['success'])) {
  echo 'Thank you for registering. Please check your email.';
}
?>
<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<link rel="stylesheet" type="text/css" href="css/style.css" >
	<title>Register</title>
</head>
<body>	
	<div id="container">
		<ul>
			<li><a href="index.php">Home</a></li>
			<li><a href="register.php">Register</a></li>
			<li><a href="login.php">Login</a></li>
		</ul>
		<h1>Register</h1>

		<form method="post" action="">
			<h4>Username:</h4>
			<input type="text" name="username" />
			<h4>Password:</h4>
			<input type="password" name="password" />
			<h4>Email:</h4>
			<input type="text" name="email" />	
			<br>
			<input type="submit" name="submit" />
		</form>


		<?php 
		# if there are errors, they would be displayed here.
		if(empty($errors) === false){
			echo '<p>' . implode('</p><p>', $errors) . '</p>';
		}

		?>

	</div>
</body>
</html>

Above we used 3 functions (user_exists, email_exists and register) that we will create next. We also used the built-in strlen and filter_var to make other validations.

Take note that the errors array is the array that we defined in our init.php which is included at the top.

Lets create our user_exists method, email_exists method and our register() method inside our Users class.

Add this in the Users class in users.php
<?php  

public function user_exists($username) {

	$query = $this->db->prepare("SELECT COUNT(`id`) FROM `users` WHERE `username`= ?");
	$query->bindValue(1, $username);

	try{

		$query->execute();
		$rows = $query->fetchColumn();

		if($rows == 1){
			return true;
		}else{
			return false;
		}

	} catch (PDOException $e){
		die($e->getMessage());
	}

}
 
public function email_exists($email) {

	$query = $this->db->prepare("SELECT COUNT(`id`) FROM `users` WHERE `email`= ?");
	$query->bindValue(1, $email);

	try{

		$query->execute();
		$rows = $query->fetchColumn();

		if($rows == 1){
			return true;
		}else{
			return false;
		}

	} catch (PDOException $e){
		die($e->getMessage());
	}

}

public function register($username, $password, $email){
	
	$time 		= time();
	$ip 		= $_SERVER['REMOTE_ADDR'];
	$email_code = sha1($username + microtime());
	$password   = sha1($password);

	$query 	= $this->db->prepare("INSERT INTO `users` (`username`, `password`, `email`, `ip`, `time`, `email_code`) VALUES (?, ?, ?, ?, ?, ?) ");

	$query->bindValue(1, $username);
	$query->bindValue(2, $password);
	$query->bindValue(3, $email);
	$query->bindValue(4, $ip);
	$query->bindValue(5, $time);
	$query->bindValue(6, $email_code);

	try{
		$query->execute();

		// mail($email, 'Please activate your account', "Hello " . $username. ",\r\nThank you for registering with us. Please visit the link below so we can activate your account:\r\n\r\nhttp://www.example.com/activate.php?email=" . $email . "&email_code=" . $email_code . "\r\n\r\n-- Example team");
	}catch(PDOException $e){
		die($e->getMessage());
	}	
}

Now this is where you need to have some knowledge of PDO and OOP. Basically in all the above methods we are using the $db object which we defined in our __construct method (remember we are in users.php). In all our methods, first we prepare a statement and put a '?' where we would have user defined variables such as $username and $email. Then we bind those variables into their respective places using the PDO method bindValue. You can see how this way we can tell PDO which ones are query statements and which ones are user defined variables. And PDO does the job of automatically cleaning those variables of any SQL injection.

Warning: To keep things simple, I used the sha1() function for hashing passwords, but it is not the safest method available. The best in my opinion is using the BLOWFISH bcrypt(). We will cover that in the next part of this series.

After we have prepared the query and bind values to it, we execute it. And then get the desired result.

Also, notice that the execute function is in a try block. So if there is some error or if we have made a mistake, the try block will fail and a PDO Exception will be caught. Using try and catch blocks will give us a specific and useful indication of the problem.

Note: We used the mail function to send the email to the user. But it will usually only work when you put your website on a web host. Sending email when developing on localhost can be tricky. So if you are developing locally I suggest that you comment out the mail function for now and remove those comments only when you make your site live.

Activating the user's account

So far we have successfully registered the users information in our database, and emailed him a link to our activate.php file with the email and the randomly generated email_code as parameters. We also stored that generated email_code in our database when we registered the user. So when a user visits activate.php with email and email_code in the url, we can check the email and email_code in the url and compare the values in our database.

activate.php
<?php 
require 'core/init.php';
?>
<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<link rel="stylesheet" type="text/css" href="css/style.css" >
	<title>Activate</title>
</head>
<body>	
	<div id="container">
		<ul>
			<li><a href="index.php">Home</a></li>
			<li><a href="register.php">Register</a></li>
			<li><a href="login.php">Login</a></li>
		</ul>
		<h1>Activate your account</h1>

    	<?php
        
        if (isset($_GET['success']) === true && empty ($_GET['success']) === true) {
	        ?>
	        <h3>Thank you, we've activated your account. You're free to log in!</h3>
	        <?php
	            
        } else if (isset ($_GET['email'], $_GET['email_code']) === true) {
            
	    $email 		=trim($_GET['email']);
	    $email_code	=trim($_GET['email_code']);
            
            if ($users->email_exists($email) === false) {
                $errors[] = 'Sorry, we couldn\'t find that email address.';
            } else if ($users->activate($email, $email_code) === false) {
                $errors[] = 'Sorry, we couldn\'t activate your account.';
            }
            
	     if(empty($errors) === false){
			
		echo '<p>' . implode('</p><p>', $errors) . '</p>';	
		
	     } else {

                header('Location: activate.php?success');
                exit();

            }
        
        } else {
            header('Location: index.php');
            exit();
        }
        ?>
	</div>
</body>
</html>

The only thing new in this is the activate function that is called to check whether the supplied email/email_code matches any email/email_code in our database. Below we create that function inside our Users Class.

Add this in Users class in users.php
<?php  
	public function activate($email, $email_code) {
		
		$query = $this->db->prepare("SELECT COUNT(`id`) FROM `users` WHERE `email` = ? AND `email_code` = ? AND `confirmed` = ?");

		$query->bindValue(1, $email);
		$query->bindValue(2, $email_code);
		$query->bindValue(3, 0);

		try{

			$query->execute();
			$rows = $query->fetchColumn();

			if($rows == 1){
				
				$query_2 = $this->db->prepare("UPDATE `users` SET `confirmed` = ? WHERE `email` = ?");

				$query_2->bindValue(1, 1);
				$query_2->bindValue(2, $email);				

				$query_2->execute();
				return true;

			}else{
				return false;
			}

		} catch(PDOException $e){
			die($e->getMessage());
		}
	}

In the activate function, we take in the $email and $email_code as parameters into our function. In our first query we check if we have a row in our database that has the supplied email and the email_code. If there is such a row, then we do another query in which we update the 'confirmed' field in that row and set it to 1 from the default 0.

Logging the user in

Logging the user consists of setting something in the $_SESSION variable. Notice that we use session_start() at the start of every page, since that is top most thing in our init.php. So in login.php, all we have to do is take the user's username and password and validate it. If the user supplies the correct username/password combination, then we will set the id corresponding to that username to a $_SESSION variable. By setting the id as a variable in the session, we can know that the session is that of a logged in user and also who the user is.

First let's create the login method in our Users class, which we are going to uses shortly in our login.php

Add this in Users class in users.php
<?php  

public function login($username, $password) {

	$query = $this->db->prepare("SELECT `password`, `id` FROM `users` WHERE `username` = ?");
	$query->bindValue(1, $username);
	
	try{
		
		$query->execute();
		$data 				= $query->fetch();
		$stored_password 	= $data['password'];
		$id 				= $data['id'];
		
		#hashing the supplied password and comparing it with the stored hashed password.
		if($stored_password === sha1($password)){
			return $id;	
		}else{
			return false;	
		}

	}catch(PDOException $e){
		die($e->getMessage());
	}
}

So in this function we take in the username and the password. The query selects the password and id for that username. Further down we check if the sha1 hash of the supplied password is equal to the stored password, which was also hashed using the same sha1 hashing method. If they are equal then we return the selected id otherwise we return false.

Before we login the user, we will also need to check if the user has activated his account, so we need to create another function in our Users class.

Add this in Users class in users.php
<?php
public function email_confirmed($username) {

	$query = $this->db->prepare("SELECT COUNT(`id`) FROM `users` WHERE `username`= ? AND `confirmed` = ?");
	$query->bindValue(1, $username);
	$query->bindValue(2, 1);
	
	try{
		
		$query->execute();
		$rows = $query->fetchColumn();

		if($rows == 1){
			return true;
		}else{
			return false;
		}

	} catch(PDOException $e){
		die($e->getMessage());
	}

}
?>

In the above function, we only check if the supplied username has the 'confirmed' field in the 'users' table set to 1 or not. If it is 1 we return true otherwise we return false.

Now we will create our login.php and use the two function we just created.

login.php
<?php
require 'core/init.php';

if (empty($_POST) === false) {

	$username = trim($_POST['username']);
	$password = trim($_POST['password']);

	if (empty($username) === true || empty($password) === true) {
		$errors[] = 'Sorry, but we need your username and password.';
	} else if ($users->user_exists($username) === false) {
		$errors[] = 'Sorry that username doesn\'t exists.';
	} else if ($users->email_confirmed($username) === false) {
		$errors[] = 'Sorry, but you need to activate your account. 
					 Please check your email.';
	} else {

		$login = $users->login($username, $password);
		if ($login === false) {
			$errors[] = 'Sorry, that username/password is invalid';
		}else {
			// username/password is correct and the login method of the $users object returns the user's id, which is stored in $login.

 			$_SESSION['id'] =  $login; // The user's id is now set into the user's session  in the form of $_SESSION['id'] 
			
			#Redirect the user to home.php.
			header('Location: home.php');
			exit();
		}
	}
} 
?>
<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<link rel="stylesheet" type="text/css" href="css/style.css" >
	<title>Login</title>
</head>
<body>	
	<div id="container">
		<ul>
			<li><a href="index.php">Home</a></li>
			<li><a href="register.php">Register</a></li>
			<li><a href="login.php">Login</a></li>
		</ul>
		<h1>Login</h1>

		<?php if(empty($errors) === false){

			echo '<p>' . implode('</p><p>', $errors) . '</p>';			

		} 
		?>
		<form method="post" action="">
			<h4>Username:</h4>
			<input type="text" name="username">
			<h4>Password:</h4>
			<input type="password" name="password">
			<br>
			<input type="submit" name="submit">
		</form>
	</div>
</body>
</html>

The login.php is similar to register.php. We have a form where we take the username and password. We make regular checks at the top such as whether the username exists or whether the user has confirmed his account. If these tests fail, errors regarding that test are added to the errors array. Which is then displayed above the form.

If all the tests succeed and if there are no errors, then we call the login function using our $users object. If the supplied username/password is incorrect the login function will return false and appropriate error will be displayed. However, if the username/password is correct, then the user's id will be set in the $_SESSION variable and the user will be directed to the home.php page.

Homepage for the user after login.

So this is similar to what happens when you login to Facebook, which is after you have logged in you are directed to your Facebook homepage where you have your personalized data displayed such as your News feed, your friend list and other things. We however, will only display the user's personal username :)

After the user has logged in, we have the user's id stored in the session. So we can use that to get all the data we need from our table for this id. We create yet another function in our Users class to get user's information using the user's id.

Add this in Users class in users.php
<?php  
public function userdata($id) {

	$query = $this->db->prepare("SELECT * FROM `users` WHERE `id`= ?");
	$query->bindValue(1, $id);

	try{

		$query->execute();

		return $query->fetch();

	} catch(PDOException $e){

		die($e->getMessage());
	}
}

The above function will return the user's data in the form of an array.

Over to our home.php file
<?php 
require 'core/init.php';

$user 		= $users->userdata($_SESSION['id']);
$username 	= $user['username'];

?>
<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<link rel="stylesheet" type="text/css" href="css/style.css" >
	<title>Home</title>
</head>
<body>	
	<div id="container">
		<ul>

			<li><a href="index.php">Home</a></li>
			<li><a href="logout.php">Logout</a></li>

		</ul>
		<h1>Hello <?php echo $username, '!'; ?></h1><!-- This will say Hello sunny! for example -->
	</div>
</body>
</html>

We call the userdata() method of the $users object and store the 'username' index in the variable $username, and echo out 'Hello sunny!'(for example). Also notice the navigation has link to home.php and also logout.php which we will create next.

Logging the user out

logout.php
<?php 
session_start();
session_destroy();
header('Location:index.php');
?>

This is it. This will successfully destroy the user's session and redirect him/her to index.php

Restricting access to users

You might be wondering that we created the our $general object and its corresponding class General, but we never used it. We will create the following functions in the General class.

Add this in the General class in general.php
<?php  
	#Check if the user is logged in.
	public function logged_in () {
		return(isset($_SESSION['id'])) ? true : false;
	}

	#if logged in then redirect to home.php
	public function logged_in_protect() {
		if ($this->logged_in() === true) {
			header('Location: home.php');
			exit();		
		}
	}
	
	#if not logged in then redirect to index.php
	public function logged_out_protect() {
		if ($this->logged_in() === false) {
			header('Location: index.php');
			exit();
		}	
	}
Note: Inside logged_in_protect() and logged_out_protect() we use the $this operator to call the function $logged_in() because that function is in the same class as the two functions. Just like in our Users class we used $this operator to use the $db parameter(variable) that was defined inside that class.

The reason we created the last two functions is to restrict access to pages from users that should not see them. For example we don't want a logged in user to be able to visit register.php or login.php. In the same way we don't want users that are not logged in to visit home.php

So you need to put logged_in_protect() and logged_out_protect() at the top of different pages accordingly.

For example, we will call the logged_in_protect() function at the top of login.php right after we include init.php. Like this:

login.php
<?php 
require 'core/init.php';
$general->logged_in_protect();
?>
<!-- 
.
.
.
Rest of the login.php
.
.
.
 -->

Similarly we will call logged_out_protect() at the top of home.php

home.php
<?php 
require 'core/init.php';
$general->logged_out_protect();
?>
<!--
.
.
.
Rest of the home.php
.
.
.
 -->

Along with login.php, you need to call logged_in_protect() at the top of register.php, activate.php and index.php

Because our application only has one page(home.php) that is exclusive for a logged in user, we only need to call logged_out_protect() at the top of home.php.

Creating the list of the registered users

Lets wrap up and create our members.php file, where we list all the registered users. For this we will create our final function of the tutorial, which will go in our Users class.

For the last time add this in Users class in users.php
<?php  

public function get_users() {

	#preparing a statement that will select all the registered users, with the most recent ones first.
	$query = $this->db->prepare("SELECT * FROM `users` ORDER BY `time` DESC");
	
	try{
		$query->execute();
	}catch(PDOException $e){
		die($e->getMessage());
	}

	# We use fetchAll() instead of fetch() to get an array of all the selected records.
	return $query->fetchAll();
}
?>
members.php
<?php 
require 'core/init.php';

$members 		= $users->get_users();
$member_count = count($members);
?>

<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<link rel="stylesheet" type="text/css" href="css/style.css" >
	<title>Members</title>
</head>
<body>	
	<div id="container">
		<h1>Our members</h1>
		<p>We have a total of <strong><?php echo $member_count; ?></strong> registered users.</p>

		<?php 
                #Showing the username and the date of joining, using the date() function.
		foreach ($members as $member) {
			echo '<p>',$member['username'], ' joined: ', date('F j, Y', $member['time']), '</p>';
		}
		?>
	</div>
</body>
</html>

Conclusion

Since the purpose of this tutorial was to create a practical example of an application using OOP and PDO together and to keep the tutorial as short as possible, I did not cover few things:

  • Denying access to your core folder, so people cannot access it from their browsers. You can easily do that using a htaccess file.
  • In our login.php and register.php each time there is an error, the user will have to re-enter everything again in the form. This can be frustrating, what you can do is when the page refreshes with an error you can echo out the value stored in $_POST as values of the input fields accordingly, but make sure you clean them by using the Php's htmlentities function before echoing them out.
  • We also missed out an important feature of having a 'forgot your password' functionality. You can most simply do this by generating a random password and emailing that to the user, when a user asks for it.

In the Part 2 of the tutorial: Login and Registration with Object Oriented Php and PDO Part 2 we will add and change a number of things, such as using bcrypt() instead of sha1(), adding some more security measures, adding the above listed items and also creating user's profile and settings functionality. Thank you very much for reading, please share your comments below and see you in the next part.

Follow us on Twitter!
comments powered by Disqus