How To Create A Login And Signup System In PHP Using PDO

In this tutorial, I’ll show you how to create a simple login and signup system using PHP’S PDO in an Object-Oriented Fashion. PHP has a reputable number of Api’s for communicating with the Mysql database but for this lesson, we would be using PDO to communicate with our Mysql database.

Why Would You Want To Use PDO?

PDO has a great number of advantages such as security and multiple driver support and that means you can easily modify your PHP scripts to support a different database driver. Sounds interesting, but there is more. With PDO, there are many helper functions which we will be covering later in this lesson to help automate routine operations.

Glossary

I came across different terms or tech jargons related to PHP, PDO, and OOP for this lesson.

  1. DSN: A data source name (DSN) is a data structure that contains the information about a specific database that an Open Database Connectivity ( ODBC ) driver needs in order to connect to it.

  2. METHOD: In a lame man term, a method is a function inside of a class. But in order to widen your scope, a method in object-oriented programming is a procedure associated with a class. A method defines the behavior of the objects that are created from the class. Another way to say this is that a method is an action that an object is able to perform.

  3. MAGIC METHOD: PHP reserves all function names starting with as magical. These are methods that allow to respond to specified circumstances when using a specific object.

  4. MODEL: A Model is a representation of some kind of data and has the methods to change them. For example: you never put SQL queries in a Controller, those are put in the Model and the Controller will call upon the Model to execute the queries.

  5. CONTROLLER: Controllers can group related route logic into a class, as well as take advantage of more advanced framework features such as automatic dependency injection.

  6. CRUD: In computer programming, create, read, update, and delete (CRUD) are the four basic functions of persistent storage. Read more

Project Requirements

In order to get the best out of this lesson, the following requirement needs to be satisfied.

  1. A Localhost Server: You can download the latest version of any of the following. XAMPP, LAMPP, MAMPP, and AMPPS depending on your operating system.

  2. A text editor: A text editor to write your codes with. Personally, I recommend you download Vs Code.

  3. A Css Framework: A Css framework such as MaterializeCss, Bootstrap4, or SkeletonCss. It all depends on whichever Css framework you prefer but for this lecture, I recommend Bootstrap.

  4. Basic Knowledge of PHP.

  5. Download the Mysql Database from Github.

With the requirements all satisfied, we can then now take some time to talk about PDO (PHP DATA OBJECTS).

PDO (PHP DATA OBJECTS)

> The PHP Data Objects (PDO) extension defines a lightweight, consistent interface for accessing databases in PHP. Each database driver that implements the PDO interface can expose database-specific features as regular extension functions. Note that you cannot perform any database functions using the PDO extension by itself; you must use a database-specific PDO driver to access a database server.

> PDO provides a data-access abstraction layer, which means that, regardless of which database you’re using, you use the same functions to issue queries and fetch data. PDO does not provide a database abstraction; it doesn’t rewrite SQL or emulate missing features. You should use a full-blown abstraction layer if you need that facility.

> PDO ships with PHP 5.1, and is available as a PECL extension for PHP 5.0; PDO requires the new OO features in the core of PHP 5, and so will not run with earlier versions of PHP.

Back to our project, it’s high time we get our hands dirty and write some codes. In your localhost htdocs, www, or html directory, we would create a new folder to house our application. You can name the folder whatever you want.

Inside of the just created folder, we should have a working directory that looks like the following.

Project Directory

*/ ld-auth (Parent Folder or Project)
    */ assets (Our Css And Js Lives Here)
        */ css
    */controller
        /Controller.php
        /Dashboard.php
        /Login.php
        /Logout.php
        /Register.php
    */Model
        /DashboardModel.php
        /Db.php
        /LoginModel.php
        /RegisterModel.php
    dashboard.php
    index.php
    logout.php
    nav.php
    register.php    

1. Editing Our Db.php (Model Folder)

In the Model folder, the Db.php file would be holding all the necessary configurations needed to establish a connection with our Mysql database.

Our Db.php file is also acting as a base class for every other class in the Model folder.

With that said, our Db.php should contain the following.

<?php
class Db {
protected $dbName = 'learning_dollars_db'; /** Database Name */
protected $dbHost = 'localhost'; /** Database Host */
protected $dbUser = 'root'; /** Database Root */
protected $dbPass = ''; /** Databse Password */
protected $dbHandler, $dbStmt;
/**
* @param null|void
* @return null|void
* @desc Creates or resume an existing database connection...
**/
public function __construct()
{
// Create a DSN Resource...
$Dsn = "mysql:host=" . $this->dbHost . ';dbname=' . $this->dbName;
//create a pdo options array
$Options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
try {
$this->dbHandler = new PDO($Dsn, $this->dbUser, $this->dbPass, $Options);
} catch (Exception $e) {
var_dump('Couldn\'t Establish A Database Connection. Due to the following reason: ' . $e->getMessage());
}
}
/**
* @param string
* @return null|void
* @desc Creates a PDO statement object
**/
public function query($query)
{
$this->dbStmt = $this->dbHandler->prepare($query);
}
/**
* @param string|integer|
* @return null|void
* @desc Matches the correct datatype to the PDO Statement Object.
**/
public function bind($param, $value, $type = null)
{
if (is_null($type)) {
switch (true) {
case is_int($value):
$type = PDO::PARAM_INT;
break;
case is_bool($value):
$type = PDO::PARAM_BOOL;
break;
case is_null($value):
$type = PDO::PARAM_NULL;
break;
default:
$type = PDO::PARAM_STR;
break;
}
}
$this->dbStmt->bindValue($param, $value, $type);
}
/**
* @param null|void
* @return null|void
* @desc Executes a PDO Statement Object or a db query...
**/
public function execute()
{
$this->dbStmt->execute();
return true;
}
/**
* @param null|void
* @return null|void
* @desc Executes a PDO Statement Object an returns a single database record as an associative array...
**/
public function fetch()
{
$this->execute();
return $this->dbStmt->fetch(PDO::FETCH_ASSOC);
}
/**
* @param null|void
* @return null|void
* @desc Executes a PDO Statement Object an returns nultiple database record as an associative array...
**/
public function fetchAll()
{
$this->execute();
return $this->dbStmt->fetchAll(PDO::FETCH_ASSOC);
}
}
?>
view raw Db.php hosted with ❤ by GitHub

Our class contains some protected properties which we would be making use of in creating our DSN (Data Source Name) which is required for creating a new PDO connection.

> A data source name (DSN) is a data structure that contains the information about a specific database that an Open Database Connectivity ( ODBC ) driver needs in order to connect to it.

The __construct() Magic Method

All the magic happens in our __construct magic method, where our Dsn was created alongside some sane configs / attributes for our PDO connection.

  1. PDO::ATTR_PERSISTENT: This attribute is responsible for creating a persistent database connection. You can visit the php.net website to learn more about persistent database connection.

  2. PDO::ATTR_ERRMODE: This attribute determines how an exception or an error is handled when an exception is thrown when the PDO object tries making or handling some requests with the database server.

You can visit the php.net website to learn more about the different attributes associated with PDO.

With that explanation out of the way, A new database connection was created and passed to the Db Class protected $dbHandler property which makes it possible to reference the created database connection in the current Class methods and other Classes that extends the Db Class.

We could have just gotten lazy and use the database connection created from the PDO instance without extending it further but that would probably make the application difficult to maintain because we would be referencing a lot of PDO methods in different files which is not really DRY (Don’t Repeat Yourself).

So, in order to tackle that, using an Object-Oriented Approach works best here because we have all the PDO methods in one place which provides a decent level of abstraction as we only need to call methods available in the Db Class to achieve a particular task.

> Abstraction is selecting data from a larger pool to show only the relevant details of the object to the user. Abstraction “shows” only the essential attributes and “hides” unnecessary information. It helps to reduce programming complexity and effort. It is one of the most important concepts of OOPs.

The Query Method

The query method which accepts a single argument and returns null or void is responsible for reusing the database connection that was created and passed over to the $dbHandler protected variable.

This is used in invoking the prepare method which accepts the string from the query method. A statement object is created and passed on to another protected property $dbStmt.

From the php.net website, The line below best explains the prepare method.

> Prepares an SQL statement to be executed by the PDOStatement::execute() method. The statement template can contain zero or more named (:name) or question mark (?) parameter markers for which real values will be substituted when the statement is executed. Both named and question mark parameter markers cannot be used within the same statement template; only one or the other parameter style. Use these parameters to bind any user-input, do not include the user-input directly in the query.

The Bind Method

The bind method accepts 3 parameters, $param, $value, and $type. The method then runs a switch statement if the $type is null in order to bind the correct data type to $type.

Available PDO Data Types:

  1. PDO::PARAM_BOOL: Represents a boolean data type.

  2. PDO::PARAM_NULL: Represents the SQL NULL data type.

  3. PDO::PARAM_INT: Represents the SQL INTEGER data type.

  4. PDO::PARAM_STR: Represents the SQL CHAR, VARCHAR, or other string data type.

The bind method also makes an attempt to bind the correct values to the ? or : placeholder generated from the statement object in the query method.

According to Google,

> The PDOStatement::bindValue() function is an inbuilt function in PHP which is used to bind a value to a parameter. This function binds a value to corresponding named or question mark placeholder in the SQL which is used to prepare the statement.

The Execute Method

The execute method receives no parameters and returns a boolean true or a boolean false. This method attempts to invoke or call PDO’s execute method which executes the prepared statement or the generated statement object.

> PDOStatement::execute returns a boolean true on success or a boolean false on failure.

The Fetch Method

The fetch method invokes the Db Class execute method which executes the generated PDO prepared statement and the fetch method which also calls another PDO method called fetch which takes a single parameter and determines how the result is processed and returns it using the format passed in as an argument.

You can visit the php.net website to read the full docs.

The FetchAll Method

The fetchAll method also invokes the Db Class execute method which was explained earlier. The only difference is that the fetchAll method calls a PDO method called fetch_all on the prepared statement which takes a single argument and returns a list containing all of the matched records in the database depending on the prepared statement condition.

You can visit the php.net website to read the full docs.

2. Editing Our LoginModel.php (Model Folder)

The LoginModel houses a class that extends the Db Class from the Db.php file in the Model Folder.

The LoginModel Class contains a single method that searches for a user based on an email address and returns an array.

Open the LoginModel.php file and paste the code snippet below.

<?php
require_once(__dir__ . '/Db.php');
class LoginModel extends Db {
/**
* @param string
* @return array
* @desc Returns a user record based on the method parameter....
**/
public function fetchEmail(string $email) :array
{
$this->query("SELECT * FROM `db_user` WHERE `email` = :email");
$this->bind('email', $email);
$this->execute();
$Email = $this->fetch();
if (empty($Email)) {
$Response = array(
'status' => true,
'data' => $Email
);
return $Response;
}
if (!empty($Email)) {
$Response = array(
'status' => false,
'data' => $Email
);
return $Response;
}
}
}
?>
view raw LoginModel.php hosted with ❤ by GitHub

The FetchEmail Method

The fetchEmail method takes an $email as the only parameter and returns an array depending on the result of the previous operation. The fetchEmail function makes use of the Db Class methods such as

  1. The query method.

  2. The bind method.

  3. The execute method.

  4. The fetch method.

3. Editing Our RegisterModel.php (Model Folder)

The RegisterModel file extends the Db Class which is required at the top of the file. The RegisterModel Class has two methods createUser and fetchUser which is responsible for creating a new user and returning a new user.

Your RegisterModel.php file should contain the following code snippet.

<?php
require_once(__dir__ . '/Db.php');
class RegisterModel extends Db {
/**
* @param array
* @return array
* @desc Creates and returns a user record....
**/
public function createUser(array $user) :array
{
$this->query("INSERT INTO `db_user` (name, email, phone_no, password) VALUES (:name, :email, :phone_no, :password)");
$this->bind('name', $user['name']);
$this->bind('email', $user['email']);
$this->bind('phone_no', $user['phone']);
$this->bind('password', $user['password']);
if ($this->execute()) {
$Response = array(
'status' => true,
);
return $Response;
} else {
$Response = array(
'status' => false
);
return $Response;
}
}
/**
* @param string
* @return array
* @desc Returns a user record based on the method parameter....
**/
public function fetchUser(string $email) :array
{
$this->query("SELECT * FROM `db_user` WHERE `email` = :email");
$this->bind('email', $email);
$this->execute();
$User = $this->fetch();
if (!empty($User)) {
$Response = array(
'status' => true,
'data' => $User
);
return $Response;
}
return array(
'status' => false,
'data' => []
);
}
}
?>
view raw RegisterModel.php hosted with ❤ by GitHub

The CreateUser Method

The createUser method accepts an array as the only parameter and returns an array depending on the result of the Database operation. The createUser method makes use of the provided methods in the Db Class.

The FetchUser Method

The fetchUser method accepts an email string as the only parameter and returns an array containing a user with the email depending on the result of the Database operation. The method returns a User resource with the email address.

4. Editing The DashboardModel.php (Model Folder)

The DashboardModel.php file houses the Db Class from the Db.php file. The DashboardModel extends the Db Class and makes use of the method provided in the Db Class in order to return an array of news.

With that said, Our DashboardModel.php file should look like the line below.

<?php
require_once(__dir__ . '/Db.php');
class DashboardModel extends Db {
/**
* @param null
* @return array
* @desc Returns an array of news....
**/
public function fetchNews() :array
{
$this->query("SELECT * FROM `db_news` ORDER BY `id` DESC");
$this->execute();
$News = $this->fetchAll();
if (count($News) > 0) {
$Response = array(
'status' => true,
'data' => $News
);
return $Response;
}
$Response = array(
'status' => false,
'data' => []
);
return $Response;
}
}
?>
view raw DashboardModel.php hosted with ❤ by GitHub

The FetchNews Method

The fetchNews method receives no argument or parameter but returns an array of news by making use of the methods provided in the Db Class.

5. Editing The Controller.php (Controller Folder)

This file acts as the base Controller for other Controllers in the Controller folder as this makes it easier to reuse methods since other Controllers extend the base Controller.

The Controller.php file should look like the line below.

<?php
session_start();
class Controller { }
?>
view raw Controller.php hosted with ❤ by GitHub

This Class doesn’t do much only that it starts or resumes an existing session. which can then now be taken advantage of in other Classes or Controllers that extends the Controller.php

6. Editing The Dashboard Controller

This file houses a class called Dashboard Controller which extends the base Controller. The file also loads in some dependencies by requiring the DashboardModel we created earlier for fetching news.

With that said, Our Dashboard.php file should look like the following.

<?php
require_once(__dir__ . '/Controller.php');
require_once('./Model/DashboardModel.php');
class Dashboard extends Controller {
public $active = 'dashboard'; //for highlighting the active link...
private $dashboardModel;
/**
* @param null|void
* @return null|void
* @desc Checks if the user session is set and creates a new instance of the DashboardModel...
**/
public function __construct()
{
if (!isset($_SESSION['auth_status'])) header("Location: index.php");
$this->dashboardModel = new DashboardModel();
}
/**
* @param null|void
* @return array
* @desc Returns an array of news by calling the DashboardModel fetchNews method...
**/
public function getNews() :array
{
return $this->dashboardModel->fetchNews();
}
}
?>
view raw Dashboard.php hosted with ❤ by GitHub

The __construct Magic Method

This method is executed as soon as an instance of the Dashboard Class is created. This method checks if the auth_status key is set in the session array and creates a new instance of the DashboardModel class if it exists else it performs an HTTP redirect to the login page.

The GetNews Method

This method makes use of the private $dashboardModel property which is an instance of the DashboardModel Class. The $dashboardModel exposes the methods available in the DashboardModel Class which makes it easier to get the list of news.

7. Editing The Login.php (Controller Folder)

This file houses a class called Login Controller which extends the base Controller. The file also loads in some dependencies by requiring the LoginModel for fetching a user based on the user email address.

With that said, Our Login.php file should look like the following.

<?php
require_once(__dir__ . '/Controller.php');
require_once('./Model/LoginModel.php');
class Login extends Controller {
public $active = 'login'; //for highlighting the active link...
private $loginModel;
/**
* @param null|void
* @return null|void
* @desc Checks if the user session is set and creates a new instance of the LoginModel...
**/
public function __construct()
{
if (isset($_SESSION['auth_status'])) header("Location: dashboard.php");
$this->loginModel = new LoginModel();
}
/**
* @param array
* @return array|boolean
* @desc Verifies and redirects a user by calling the login method on the LoginModel...
**/
public function login(array $data)
{
$email = stripcslashes(strip_tags($data['email']));
$password = stripcslashes(strip_tags($data['password']));
$EmailRecords = $this->loginModel->fetchEmail($email);
if (!$EmailRecords['status']) {
if (password_verify($password, $EmailRecords['data']['password'])) {
//check if the remember_me was selected...
$Response = array(
'status' => true
);
$_SESSION['data'] = $EmailRecords['data'];
$_SESSION['auth_status'] = true;
header("Location: dashboard.php");
}
$Response = array(
'status' => false,
);
return $Response;
}
$Response = array(
'status' => false,
);
return $Response;
}
}
?>
view raw Login.php hosted with ❤ by GitHub

The __construct Magic Method

This method is executed as soon as an instance of the Login Class is created. This method creates a new instance of the LoginModel class which is passed to the private $loginModel data property.

The Login Method

This method handles all the business logic for authenticating a user. The method receives an array and returns an array if the user fails authentication else an Http Redirect is returned.

8. Editing The Register.php (Controller Folder)

This file houses a class called Register Controller which extends the base Controller. It also requires the RegisterModel as a dependency. which is instantiated when a new instance of the Register Class is created.

With that said, The Register.php should look like the line below.

<?php
require_once(__dir__ . '/Controller.php');
require_once('./Model/RegisterModel.php');
class Register extends Controller {
public $active = 'Register'; //for highlighting the active link...
private $registerModel;
/**
* @param null|void
* @return null|void
* @desc Checks if the user session is set and creates a new instance of the RegisterModel...
**/
public function __construct()
{
if (isset($_SESSION['auth_status'])) header("Location: dashboard.php");
$this->registerModel = new RegisterModel();
}
/**
* @param array
* @return array|boolean
* @desc Verifies, Creates, and returns a user by calling the register method on the RegisterModel...
**/
public function register(array $data)
{
$name = stripcslashes(strip_tags($data['name']));
$email = stripcslashes(strip_tags($data['email']));
$phone = stripcslashes(strip_tags($data['phone']));
$password = stripcslashes(strip_tags($data['password']));
$EmailStatus = $this->registerModel->fetchUser($email)['status'];
$Error = array(
'name' => '',
'email' => '',
'phone' => '',
'password' => '',
'status' => false
);
if (preg_match('/[^A-Za-z\s]/', $name)) {
$Error['name'] = 'Only Alphabets are allowed.';
return $Error;
}
if (!empty($EmailStatus)) {
$Error['email'] = 'Sorry. This Email Address has been taken.';
return $Error;
}
if (preg_match('/[^0-9_]/', $phone)) {
$Error['phone'] = 'Please, use a valid phone number.';
return $Error;
}
if (strlen($password) < 7) {
$Error['password'] = 'Please, use a stronger password.';
return $Error;
}
$Payload = array(
'name' => $name,
'email' => $email,
'phone' => $phone,
'password' => password_hash($password, PASSWORD_BCRYPT)
);
$Response = $this->registerModel->createUser($Payload);
$Data = $this->registerModel->fetchUser($email)['data'];
unset($Data['password']); //Makes a whole lot of sense to get rid of any critical information...
if (!$Response['status']) {
$Response['status'] = 'Sorry, An unexpected error occurred and your request could not be completed.';
return $Response;
}
$_SESSION['data'] = $Data;
$_SESSION['auth_status'] = true;
header("Location: dashboard.php");
return true;
}
}
?>
view raw Register.php hosted with ❤ by GitHub

The __construct Method

This method creates a new instance of the RegisterModel class and passes the object to the Register Class protected $registerModel property.

The Register Method

This method handles all the business logic for creating a new user. The method receives an array and returns an array if the user fails verification else an Http Redirect is returned and a new session is created for the user.

9. Editing The Logout.php (Controller Folder)

This file houses a class called Logout Controller which extends the base Controller.

With that said, The Logout.php should look like the line below.

<?php
require_once(__dir__ . '/Controller.php');
class Logout extends Controller {
/**
* @param null|void
* @return null|void
* @desc Destroys the application session and redirects to the login page...
**/
public function __construct()
{
session_destroy();
header("Location: index.php");
}
}
?>
view raw Logout.php hosted with ❤ by GitHub

The __construct Magic Method

The __construct method destroys the application’s session and performs an HTTP Redirect to the login page.

10. Creating Our View Files

Since the Controllers and the Models have been created successfully, we can now begin creating our view files. The view files are going to live in the project’s root directory.

This is not really the best approach but for a bigger application, you would probably want to keep the view files in a separate folder to keeps things neat and professional.

This is basically going to be a simple HTML form with some alerts to display errors.

Editing The Nav.php (Root Dir)

The nav.php would be serving as our view’s base navigation. All other views would be extending the nav.php. In our nav.php, we have a simple PHP code island that prints the title of the current file and highlights the currently active file.

The nav.php also loads in and uses a config directory to handle proper routing for our navigation which looks like the snippet below.

<?php
define("BASE_URL", 'http://localhost/&#39; . basename(__DIR__) . '/');
?>
view raw config.php hosted with ❤ by GitHub

With that said, our nav.php file should look like the following.

<?php require_once('./config.php'); ?>
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="application-name" content="LD Talent Login Project">
<meta name="author" content="Ilori Stephen A">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>LD Talent | <?php echo ucfirst($active); ?></title>
<!-- Css Styles... -->
<link rel="stylesheet" href="./assets/css/bootstrap.min.css">
<link rel="stylesheet" href="./assets/css/style.css">
<!-- Script -->
<script src="./assets/js/jquery.js" charset="utf-8"></script>
<script src="./assets/js/bootstrap.min.js" charset="utf-8"></script>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container">
<a class="navbar-brand" href="#">LD Talent</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav ml-auto">
<?php if (!isset($_SESSION['auth_status'])) : ?>
<li class="nav-item">
<a class="nav-link <?php if (strtolower($active) === 'login') echo 'active'; ?>" href="<?php echo BASE_URL; ?>index.php">Login</a>
</li>
<li class="nav-item">
<a class="nav-link <?php if (strtolower($active) === 'register') echo 'active'; ?>" href="<?php echo BASE_URL; ?>register.php" tabindex="-1" aria-disabled="true">Register</a>
</li>
<?php elseif (isset($_SESSION['auth_status'])) : ?>
<li class="nav-item">
<a href="<?php echo BASE_URL; ?>dashboard.php" class="nav-link <?php if (strtolower($active) === 'dashboard') echo 'active'; ?>">Dashboard</a>
</li>
<?php endif; ?>
<?php if (isset($_SESSION['auth_status'])) : ?>
<li class="nav-item">
<a class="nav-link" href="<?php echo BASE_URL; ?>logout.php">Logout</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
view raw nav.php hosted with ❤ by GitHub

Editing The Index.php (Root Dir)

The index.php file acts as our login page as you will find it requires the Login Controller Class at the top of the file. A new instance of the class is created and the login method is only called when an HTTP POST request is made.

With that explanation, copy the following code snippet into the index.php file.

<?php require_once('./controller/Login.php'); ?>
<?php
$Login = new Login();
$Response = [];
$active = $Login->active;
if (isset($_POST) && count($_POST) > 0) $Response = $Login->login($_POST);
?>
<?php require('./nav.php'); ?>
<main role="main" class="container">
<div class="container">
<div class="row justify-content-center mt-10">
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-4 col-lg-4 center-align center-block">
<?php if (isset($Response['status']) && !$Response['status']) : ?>
<div class="alert alert-danger" role="alert">
<span><B>Oops!</B> Invalid Credentials Used.</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true" class="text-danger">&times;</span>
</button>
</div>
<?php endif; ?>
<div class="card shadow-lg p-3 mb-5 bg-white rounded">
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" class="form-signin">
<h4 class="h3 mb-3 font-weight-normal text-center">Sign in</h4>
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-12 col-lg-12 mt-4">
<div class="form-group">
<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control" placeholder="Email address" name="email" required autofocus>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-12 col-lg-12">
<div class="form-group">
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-12 col-lg-12">
<button class="btn btn-md btn-primary btn-block" type="submit">Sign in</button>
</div>
<p class="mt-5 text-center mb-3 text-muted">&copy; Ilori Stephen A <?php echo date('Y'); ?></p>
</form>
</div>
</div>
</div>
</div>
</main>
</body>
</html>
view raw index.php hosted with ❤ by GitHub

Editing The Register.php (Root Dir)

The register.php file contains the HTML form for creating a new user. The Register Controller Class is required at the top of the file. A new instance of the class is created and the register method is only called when an HTTP POST request is made.

If the user fails validation, the errors are passed down to the $Response variable which is rendered conditionally by checking for a key in the array.

The register.php file should look like the line below.

<?php require_once('./controller/Register.php'); ?>
<?php
$Register = new Register();
$Response = [];
$active = $Register->active;
if (isset($_POST) && count($_POST) > 0) $Response = $Register->register($_POST);
?>
<?php require('./nav.php'); ?>
<main role="main" class="container">
<div class="container">
<div class="row justify-content-center mt-5">
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-4 col-lg-4 center-align center-block">
<?php if (isset($Response['status']) && !$Response['status']) : ?>
<br>
<div class="alert alert-danger" role="alert">
<span><B>Oops!</B> Some errors occurred in your form.</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true" class="text-danger">&times;</span>
</button>
</div>
<?php endif; ?>
<div class="card shadow-lg p-3 mb-5 bg-white rounded">
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" class="form-signin">
<h4 class="h3 mb-3 font-weight-normal text-center">Register</h4>
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-12 col-lg-12 mt-4">
<div class="form-group">
<label for="inputName" class="sr-only">Names</label>
<input type="text" id="inputName" class="form-control" placeholder="Enter Full Name" name="name" required autofocus value="<?php if (isset($_POST['name'])) echo $_POST['name']; ?>">
<?php if (isset($Response['name']) && !empty($Response['name'])): ?>
<small class="text-danger"><?php echo $Response['name']; ?></small>
<?php endif; ?>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-12 col-lg-12 mt-4">
<div class="form-group">
<label for="inputEmail" class="sr-only">Email</label>
<input type="email" id="inputEmail" class="form-control" placeholder="Enter Email Address" name="email" required autofocus value="<?php if (isset($_POST['email'])) echo $_POST['email']; ?>">
<?php if (isset($Response['email']) && !empty($Response['email'])): ?>
<small class="text-danger"><?php echo $Response['email']; ?></small>
<?php endif; ?>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-12 col-lg-12 mt-4">
<div class="form-group">
<label for="inputPhone" class="sr-only">Phone Number</label>
<input type="text" id="inputPhone" class="form-control" placeholder="Enter Phone" name="phone" required autofocus value="<?php if (isset($_POST['phone'])) echo $_POST['phone'] ?>">
<?php if (isset($Response['phone']) && !empty($Response['phone'])): ?>
<small class="text-danger"><?php echo $Response['phone']; ?></small>
<?php endif; ?>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-12 col-lg-12">
<div class="form-group">
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
<?php if (isset($Response['password']) && !empty($Response['password'])): ?>
<small class="text-danger"><?php echo $Response['password']; ?></small>
<?php endif; ?>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-12 col-lg-12">
<button class="btn btn-md btn-primary btn-block" type="submit">Register</button>
</div>
<p class="mt-5 text-center mb-3 text-muted">&copy; Ilori Stephen A <?php echo date('Y'); ?></p>
</form>
</div>
</div>
</div>
</div>
</main>
</body>
</html>
view raw register.php hosted with ❤ by GitHub

Editing The Dashboard.php (Rot Dir)

The dashboard view is where users are redirected to after a successful login or signup request. It requires the Dashboard Controller Class and creates an instance of the class which fires the getNews method in order to fetch all saved news.

The dashboard.php file should look like the line below.

<?php require_once('./controller/Dashboard.php'); ?>
<?php
$Dashboard = new Dashboard();
$Response = [];
$active = $Dashboard->active;
$News = $Dashboard->getNews();
?>
<?php require('./nav.php'); ?>
<main role="main" class="container">
<div class="container">
<div class="row mt-5">
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-12 col-lg-12">
<h2>News</h2>
<hr>
</div>
</div>
<div class="row">
<?php if ($News['status']) : ?>
<?php foreach ($News['data'] as $new) : ?>
<div class="col-xs-12 col-sm-12 col-md-12 col-xl-4 col-lg-4">
<div class="card shadow-lg p-3 mb-5 bg-white rounded">
<div class="news_title">
<h3><?php echo ucwords($new['title']); ?></h3>
</div>
<div class="news_body">
<p><?php echo $new['content']; ?> <a href="javascript:void(0)">Read More</a></p>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</main>
</body>
</html>
view raw dashboard.php hosted with ❤ by GitHub

Editing The Logout.php (Root Dir)

The logout file is where the user session is destroyed and a redirect is initiated back to the index.php view.

With that said, the logout.php file should look like the line below.

<?php require_once('./controller/Logout.php'); ?>
<?php new Logout(); ?>
view raw logout.php hosted with ❤ by GitHub

Learning Tools

There are a lot of learning tools online. But in case you are looking, I recommend the following

  1. A crash course on youtube covering PDO with Brad Traversy Youtube.

  2. The PHP official website php.net.

  3. A crash course on youtube covering OOP with Brad Traversy Youtube.

Learning Strategy

I used the learning tools above and a few PDF documentations to achieve this. Anytime I faced some bugs, I would use stack overflow to check for solutions. But watching those videos helped a lot. I recommend that you do also.

Reflective Analysis

It was a simple process for me but I was able to gain some deeper insights into PDO. If you ask me, if one can understand the different methods in PDO and create a simple CRUD system, I think such person would have a greater chance of understanding Object Oriented Programming and PHP frameworks.

In order to get more knowledge, I would recommend that you edit the registration process to send a mail to the user before activating the user account.

I also recommend that you edit the login process so it’s only users with a verified account that can log in. You can also go-ahead to create a router to restrict access to our Model and our Controller directory.

Conclusion

The project mainly focuses on the Db.php file as that is where our PHP PDO operations can be found. Other files like our model files extend the Db Class from the Db.php file.

It could have been a lot easier creating or making PDO operations in our Controller files but that would be very messy. So, we adopted an Object-Oriented Approach which also introduces separation of concerns as our Model file lives in the Model, and our Controller file lives in the Controller folder.

In order to improve this project, I would recommend that you include a .htaccess file to restrict access to the Model and the Controller directory. You could also integrate Email support for the application which would force the user to activate his / her email address in order to continue.

Get the complete project from github.