Chapter 13: Project
Congratulations on making it to the end of the book! We’ve covered a lot in the preceding chapters.
The best way to learn programming is to work on an actual project. In this chapter, we’re going to work through a project together. This project covers numerous concepts you’ve learned in the previous chapters and allows you to see how everything works together. We’ll also be covering some new miscellaneous concepts in this project. Excited? Let’s do it!
13.1 About the Project
This project involves creating a website that works like a mini blog, where users with an admin account can log in to post. When posting to the blog, the admin user can choose whether to make the post available to non-members or members.
Admin and members need to be logged in while non-members do not. Admin can read and write posts, members can read “Members only” posts after they log in, and non-members can only read “Public” posts.
When a member or admin logs in to the site, the website retrieves the last post he/she has read and notifies him/her which posts are new.
To see a demonstration of how the site works, go tohttps://learncodingfast.com/php.
13.2 Acknowledgements and Requirements
This project uses HTML, CSS, Javascript, PHP and MySQL. An understanding of HTML (especially HTML forms) and SQL is essential for the project. This chapter only covers PHP and will guide you through all the PHP code used.
All HTML, CSS, Javascript and SQL code will be provided for you. You can download them athttps://learncodingfast.com/php.
However, as the project uses Bootstrap (https://getbootstrap.com/) for the user interface and CKEditor (https://ckeditor.com/ckeditor-4/) for the text editor, their code will not be provided. Instead, links will be provided in the <head>
element to include these files from their respective content delivery networks (CDN). This means that you need an internet connection when running the code in the project.
Certain instructions (such as creating a database and user account) provided here are specifically for XAMPP. If you are not using XAMPP, you’ll have to refer to the software’s documentation for specific instructions.
Last but not least, this project uses PHP 5.5 and above. If you have just downloaded XAMPP, the PHP interpreter bundled with it is at least PHP 7.
13.3 Structure of the Project
The main folder of the project is phpproject.
Inside this main folder, we have eight files: admin.php, error.html, index.php, logout.php, read.php, signup.php, UI_include.php and write.php. These files are responsible for the user interface of the blog.
Besides the eight files, we have a sub-folder called includes.
Inside includes, we have three files: loadclasses.php, header.html and debugging.php.
We also have three sub-folders: process (contains files used for processing HTML forms), classes (contains files where we define our classes) and css (contains files with CSS code).
13.4 Creating Database, User Account and Tables
Before we begin working on the PHP code, we need to create the database, tables and user account.
First, ensure that you have started XAMPP and the Apache and MySQL servers. Next, proceed tohttp://localhost/phpmyadmin/index.phpand follow the instructions in Chapter 11.5 to create a database called “project”.
Next, create a user with the following information for the “project” database:
User Name: project_admin
Host Name: localhost
Choose your own desired password for the user and click “Go” to create the user.
Once that is done, click on the SQL tab at the top of the page and copy the following SQL code into the editor. This code can be downloaded athttps://learncodingfast.com/php.
USE project;
CREATE TABLE IF NOT EXISTS members (
username VARCHAR(100) PRIMARY KEY,
password VARCHAR(255) NOT NULL,
is_admin BOOLEAN DEFAULT false,
last_viewed int DEFAULT 0
);
CREATE TABLE IF NOT EXISTS posts(
id INT AUTO_INCREMENT PRIMARY KEY,
post_date TIMESTAMP DEFAULT NOW() NOT NULL,
username VARCHAR(100) NOT NULL,
title VARCHAR(255) NOT NULL,
post TEXT NOT NULL,
audience INT NOT NULL,
CONSTRAINT FOREIGN KEY (username) REFERENCES members(username) ON DELETE CASCADE
);
The code above creates two tables, “members” and “posts”, for the “project” database. Click on “Go” to execute the code. Once that is done, we are ready to work on the PHP code.
13.5 Editing The classes Folder
First, navigate to your htdocs folder and paste the unzipped phpproject folder into it. Next, launchhttp://localhost/phpproject/index.phpin your browser. If you see the page below, all is good.

Close your browser and navigate to the htdocs\phpproject\includes\classes folder on your computer. You should see five PHP files inside.
13.5.1 Helper.php
We’ll start with Helper.php. Open this file in Brackets; you’ll see that we’ve created a class called Helper
.
This class has no properties and constructor. Inside the class, our job is to implement five public
methods – passwordsMatch()
, isValidLength()
, isEmpty()
, isSecure()
and keepValues()
.
Let’s start with the passwordsMatch()
method. This method has two parameters, $pw1
and $pw2
. In this project, to keep our code compatible with older versions of PHP, we will not be using type declaration for functions and methods. With this in mind, try declaring the passwordsMatch()
method yourself.
Next, within the method, we need to check if the values of the two parameters are equal. If they are, we return true
. Else, we return false
. Try doing this yourself. Hint: You need to use an if-else
statement. Once you are done with the if-else
statement, the passwordsMatch()
method is complete.
Next, let’s move on to the isValidLength()
method. This method has three parameters – $str
, $min
and $max
.
$min
has a default value of 8
, while $max
has a default value of 20
. You can refer to Chapter 7.1 if you are not familiar with default values for functions (and methods).
Within the method, we need to check if the length of $str
is smaller than $min
or greater than $max
. If it is, we return false
. Else, we return true
.
Try coding the method yourself. Hint: You can use the built-in function strlen()
to get the length of $str
.
After the isValidLength()
method, we have the isEmpty()
method. This method has one parameter, $postValues
, which stores an array. The method checks if any of the elements in the array is an empty string.
Inside the method, we need to use a foreach
loop to loop through each element in $postValues
and use an if
statement to check if the element equals an empty string. If it equals, we return true
.
After looping through all the elements, if no empty string is found, we return false
. In other words, we return false
outside the foreach
loop.
Try coding this method yourself. You can refer to Chapter 6.3.5 for help on using foreach
loops.
Next, we have the isSecure()
method. This method has one parameter – $pw
– and checks if $pw
contains at least one lowercase character, one uppercase character and one digit. To do so, we need to use regular expressions. A regular expression allows us to translate the requirements above (written in English) into an expression that PHP can understand.
All regular expressions must start and end with a delimiter. This delimiter can be any non-alphanumeric, non-backslash and non-whitespace character. Often used delimiters include forward slashes ( /
), hash signs ( #
) and tildes ( ~
). We’ll use tildes in our code.
The regular expression for “at least one lowercase character” is ~[a-z]+~
, where ~
is the delimiter, [a-z]
represents the set of lowercase characters and +
represents “at least one”.
The regular expression for “at least one uppercase character” is ~[A-Z]+~
and that for “at least one digit” is ~[0-9]+~
.
To check if $pw
satisfies the regular expressions above, we need to use a built-in function called preg_match()
. This function accepts two arguments – the regular expression and the string that we want to check. The regular expression is passed as a string to the function.
To check if $pw
contains at least one lowercase character, we write
preg_match("~[a-z]+~", $pw)
This returns true
if $pw
contains at least one lowercase character. Else, it returns false
.
Our isSecure()
method needs to apply the preg_match()
function three times to check if all three requirements are met. If all three are met, it returns true
. Else, it returns false
. Try doing this yourself.
Done?
Last but not least, let’s move on to the most complicated method in the Helper
class. This method is used to preserve user input in a form when the form is not processed successfully (you can refer to Chapter 8.1.4 for more details). To do that, we need to prefill the form with the user’s previous input when the form reloads.
To prefill textboxes, we use the value
attribute. For instance,
<input type="text" value = "Hello">
prefills a textbox with “Hello”.
To prefill textareas, we enclose the text between the <textarea>
opening and closing tags. For instance,
<textarea>Hello</textarea>
prefills a textarea with “Hello”.
To preselect drop-down lists, we add the word “selected” to the selected option. For instance,
<select>
<option value = 'P'>Public</option>
<option value = 'M' selected>Members Only</option>
</select>
preselects the “Members Only” option.
Our job now is to write a method to prefill/preselect user input for us. This method is called keepValues()
and has three parameters – $val
, $type
and $attr
.
$val
represents the value submitted by the user. For textboxes and textareas, the value submitted is the text entered into the respective form elements. For drop-down lists, the value submitted is the string assigned to the value
attribute of the selected option. For instance, in the drop-down list above, if the second option is selected, the value submitted is 'M'
(not “Members Only”).
$type
represents the type of form element.
$attr
is only applicable for drop-down lists and has a default value of ''
(an empty string). It represents the string assigned to the value
attribute of a drop-down list’s option. For instance, for the drop-down list above, $attr
is 'P'
for the first option and 'M'
for the second.
Try declaring this method yourself.
Next, inside the method, we have the following switch
statement:
switch ($type){
case 'textbox':
echo "value = '$val'";
break;
case 'textarea':
//Add code here
case 'select':
//Add code here
default:
echo '';
}
This statement uses the value of $type
to determine what string to echo. The first case has been completed for you. Based on the description above, try completing the other two cases yourself by echoing a suitable string for the respective form elements.
Hint: Refer to the underlined text for each HTML form element in the description above.
For the 'select'
case, you need to use an if
statement to compare the value of $val
(which is the value submitted by the user) with $attr
(which is the string assigned to the value
attribute of an option) to decide whether to echo anything for a particular option. Got it?
You may need to read through this section more than once to complete this switch
statement.
Once you are done with the switch
statement, the keepValues()
method is complete and so is the Helper
class. Remember to close the braces for the switch
statement, the keepValues()
method and the class itself.
13.5.2 Database.php
Next, let’s move on to the Database.php file. Inside this file, we’ve created a class called Database
with three constants ( SELECTSINGLE
, SELECTALL
and EXECUTE
) and one private property ( $pdo
). In addition, we have a constructor that is used to create a new PDO
object.
You need to modify two things in the constructor. First, on line 13, you need to change “Your Password” to your actual password.
Next, notice that $pdo
is a property of the Database
class? We learned in Chapter 9.2 that to access any property of a class, you need to use the $this
keyword inside the class.
Hence, on line 13, you need to change
$pdo = …
to
$this->pdo = …
The same applies to line 14. Try doing this yourself. Once that is done, the constructor is complete.
Next, we need to add a public
method called queryDB()
to the Database
class. To code this method, you need to be familiar with using prepared statements in PHP. You can refer to Chapter 11.4 for reference if you are not familiar.
The queryDB()
method has three parameters, $sql
, $mode
and $values
.
$sql
represents the SQL statement to be executed, $mode
indicates whether the method needs to fetch any row(s) from the database and $values
, which has a default value of array()
(i.e., an empty array), is used for binding variables to the placeholders in $sql
.
Try declaring the method yourself.
Inside the method, we need to use the $pdo
property (reminder: you access it using $this->pdo
) to prepare the SQL statement ( $sql
) and assign the result to a variable called $stmt
. Try doing this yourself.
Next, we need to bind values to the placeholders in the SQL statement. The placeholders and values for binding are passed as a two-dimensional array ( $values
) to the method. Refer to Chapter 5.3.1 if you are not familiar with multidimensional arrays.
Suppose the placeholders are :username
and :password
and the variables to bind are $uname
and $pwd
respectively, users need to pass the following array to the queryDB()
method:
$values = array(
array(':username', $uname),
array(':password', $pwd)
);
To process this array, we use the foreach
loop below:
foreach($values as $valueToBind){
$stmt->bindValue($valueToBind[0], $valueToBind[1]);
}
When this loop runs for the first time, the array (':username', $uname)
gets assigned to $valueToBind
. Inside the loop, we use the bindValue()
method to bind $uname
( $valueToBind[1]
) to ':username'
( $valueToBind[0]
).
When the loop runs for the second time, we use the bindValue()
method to bind $pwd
to ':password'
. Got it?
Copy the foreach
loop above into the queryDB()
method and make sure you understand it before proceeding.
After binding values to placeholders, we need to use $stmt
to call the execute()
method. Try doing this yourself.
Finally, we need to determine if there are any values to be fetched. We do that using the second parameter – $mode
.
$mode
can take one of three constants – SELECTSINGLE
, SELECTALL
or EXECUTE
. These three constants were defined in the class previously. We use an if
statement to determine whether we should fetch any results. The if
statement works similar to the pseudocode below:
if ($mode is not equal to SELECTSINGLE, SELECTALL and EXECUTE){
throw an Exception using 'Invalid Mode' as the error message
}
else if ($mode equals SELECTSINGLE){
use $stmt to call the fetch(PDO::FETCH_ASSOC) method and return the result using the return keyword
}
else if ($mode equals SELECTALL){
use $stmt to call the fetchAll(PDO::FETCH_ASSOC) method and return the result using the return keyword
}
First, we check if the value of $mode
is valid (i.e., it must be either SELECTSINGLE
, SELECTALL
or EXECUTE
).
Next, we check if $mode
is SELECTSINGLE
or SELECTALL
and use the $stmt
variable to call the fetch(PDO::FETCH_ASSOC)
or fetchAll(PDO::FETCH_ASSOC)
method respectively. We then use the return
keyword to return the results fetched.
Try converting the pseudocode to PHP code yourself.
Hint:
To access the constants defined in the Database
class, you need to use the self
keyword or the class name followed by the ::
operator. For instance, to access EXECUTE
, you can write Database::EXECUTE
. Refer to Chapter 9.7 for reference on using constants in classes.
To throw an exception, you create a new Exception
object and pass the error message to the constructor. Refer to Chapter 12.1.3 for reference on throwing exceptions.
Once you are done with the if
statement, the queryDB()
method is complete and so is the Database
class.
13.5.3 BlogReader.php
Now, let’s proceed to the BlogReader
class. This class has two constants, READER
and MEMBER
, with values 1
and 2
respectively. In addition, it has two protected properties $db
and $type
. The constructor of the class has been coded for you.
public function __construct(){
$this->db = new Database();
$this->type = BlogReader::READER;
}
This constructor initializes the values of $db
and $type
.
Now, we need to add a method called getPostsFromDB()
to the class. This method is public
and has no parameter; try declaring it yourself.
A blog reader refers to readers of the blog who are not logged in. Readers who are not logged in can only read posts from the “posts” table where the “audience” column has a value smaller than or equal to 1. Hence, inside the getPostsFromDB()
method, we need to use the following statement to query the “posts” table:
$sql = "SELECT id, unix_timestamp(post_date) as `post_date`, username, title, post, audience FROM posts WHERE audience <= :audience ORDER BY id DESC";
Add the statement above to the getPostsFromDB()
method; we’ll use the queryDB()
method to execute it later.
This statement has one placeholder :audience
.
Based on what we mentioned when we coded the Database
class, we need to declare a two-dimensional array and use it to bind a value to the placeholder. This is done with the following statement:
$values = array(
array(':audience', $this->type)
);
Here, we declare a two dimensional array called $values
with one inner array – (':audience', $this->type)
.
We use this inner array to bind the $type
property to the :audience
placeholder. As we’ve previously assigned BlogReader::READER
(which equals 1
) to the $type
property in the constructor, we are essentially binding the value 1
to the :audience
placeholder. Got it?
Add the $values
array above to the getPostsFromDB()
method. Next, we need to use the Database
object ( $db
) to call the queryDB()
method in the Database
class. To do that, we use the statement below:
$result = $this->db->queryDB($sql, Database::SELECTALL, $values);
Here, we use $this->db
to access $db
as it is a class property. After accessing $db
, we use it to call the queryDB()
method, passing the SQL statement ( $sql
), the mode ( Database::SELECTALL
) and the $values
array to the method.
The mode is Database::SELECTALL
as we are retrieving more than one row from the “posts” table. Next, we assign the result returned to a variable called $result
.
Add the statement above to the getPostsFromDB()
method.
Finally, we need to check if there are any rows returned by the queryDB()
method. To do that, we check if there are any elements in $result
. If there aren’t, we return false
. Else, we return the $result
array. Try writing this if
statement yourself.
Hint: You can use the count()
function to get the number of elements in $result
.
Once that is done, the getPostsFromDB()
method is complete and so is the BlogReader
class. Read through the getPostsFromDB()
method carefully and make sure you understand it before proceeding; we’ll be coding many methods similar to it later.
13.5.4 BlogMember.php
Now, let’s write a class that extends the BlogReader
class.
Open BlogMember.php and create a new class called BlogMember
. This class extends the BlogReader
class and has a private
property called $username
.
Try declaring the class yourself.
Inside the class, we have a constructor that has one parameter – $pUsername
.
Inside the constructor, we need to call the parent class constructor to initialize the inherited property $db
. Next, we need to assign $pUsername
to the $username
property and BlogMember::MEMBER
(this constant is inherited from the BlogReader
class) to the inherited property $type
.
Try implementing the constructor yourself. Hint: You need to use the parent
keyword, followed by the ::
operator, to call the parent class constructor.
Got it? Good! After you have implemented the constructor, you need to implement six more methods. The methods are:
public function isDuplicateID()
public function insertIntoMemberDB($pPassword)
public function isValidLogin($pPassword)
private function getLatestPostID()
public function updateLastViewedPost()
public function getLastViewedPost()
Let’s start with the isDuplicateID()
method. This method is public
and has no parameter. It is called whenever a new user signs up and returns true
if the username selected by the new user already exists in the “members” table. Try declaring the method yourself.
Inside the method, we need to execute the following SQL statement:
SELECT count(username) AS num FROM members WHERE username = :username
This statement returns 0
if the username bound to :username
is not found.
Try using the queryDB()
method in the Database
class to execute this SQL statement. You need to bind the $username
property to :username
and use the Database::SELECTSINGLE
mode (as we are only selecting one row from the database) to call the queryDB()
method.
Try doing this yourself. You can refer to the getPostsFromDB()
method in the BlogReader
class for reference on using the queryDB()
method. Got it?
Once that is done, assign the result returned by queryDB()
to a variable called $result
.
Next, use an if
statement to check if $result['num']
is equal to zero. If it is, return false
. Else, return true
.
Try coding this method yourself.
Once you have completed the isDuplicateID()
method, we can move on to the insertIntoMemberDB()
method.
This method is public
and has one parameter – $pPassword
. It is used to insert a new row into the “members” table when a new user signs up.
$pPassword
stores the password entered by the user when he/she submits the sign-up form.
Inside the method, we need to use the queryDB()
method with the Database::EXECUTE
mode (as we are not fetching any data from the database) to execute the following SQL statement:
INSERT INTO members (username, password) VALUES (:username, :password)
To execute this statement, we need to bind $username
(the class property) to :username
and $pPassword
(the parameter) to :password
.
However, as $pPassword
stores the password selected by our user, we should not store it in the database in its original form. Instead, we should hash it first. Hashing is similar to encrypting and can be done using a built-in function in PHP called password_hash()
. This function is available from PHP 5.5 onwards and accepts two arguments – the string to hash and the algorithm to use.
The algorithm to use can be any of the predefined constants found athttps://www.php.net/manual/en/password.constants.php.
In our project, we’ll use PASSWORD_DEFAULT
as the constant. This constant indicates that we’ll use the default algorithm in PHP. To hash $pPassword
, we use the code below:
password_hash($pPassword, PASSWORD_DEFAULT)
Hence, to bind values to the :username
and :password
placeholders in our SQL statement, we use the $values
array below:
$values = array(
array(':username', $this->username),
array(':password', password_hash($pPassword, PASSWORD_DEFAULT))
);
Try using this array, the Database::EXECUTE
constant and the SQL INSERT
statement above to call the queryDB()
method. Once that is done, the insertIntoMemberDB()
method is complete.
There is no need to return any result for this method as the queryDB()
method does not return any result when the mode is Database::EXECUTE
.
Done? Great!
Let’s move on to the isValidLogin()
method. This method is public
and has one parameter – $pPassword
. It checks if the username and password entered by the user are valid when he/she tries to log in.
Inside the method, we need to use the queryDB()
method to execute the following SQL statement:
SELECT password FROM members WHERE username = :username
To do that, you need to bind the $username
property to :username
and use the Database::SELECTSINGLE
mode (as we are only selecting one row from the database) to call the queryDB()
method.
Once that is done, assign the result to a variable called $result
.
Try doing this yourself.
Next, we need to check if $result['password']
is set.
$result['password']
is set only if the fetch()
method in queryDB()
manages to fetch the password. If there is no user with the username stated in the SQL query, $result['password']
will not be set.
Besides checking if $result['password']
is set, we also need to check if the password entered by the user matches the password fetched from the database.
Recall that the password stored in the database is hashed and no longer in its original form?
To check if the hashed password matches the password entered by the user, we need to use another built-in function called password_verify()
. This function accepts two arguments and returns true
if the first argument matches the second. The second argument has to be a password hashed using the password_hash()
function.
In our isValidLogin()
method, to determine if the two passwords match, we use the following if
statement:
if (isset($result['password']) && password_verify($pPassword, $result['password']))
return true;
else
return false;
Add the statement above to the isValidLogin()
method and the method is complete.
Great! Let’s proceed to the getLatestPostID()
method. This is a private
method with no parameters and will be used in the updateLastViewedPost()
method later. Try declaring it yourself.
Inside the method, we need to use the queryDB()
method to execute the following SQL statement:
SELECT max(id) AS max FROM posts
Decide on the appropriate mode to use for this SQL statement and try calling the queryDB()
method yourself. Note that for this SQL statement, there is no need to pass any array to the queryDB()
method as the statement does not contain any placeholder.
Once you have executed the queryDB()
method, assign the result to a variable called $result
.
The SQL statement above returns NULL
if there is no post in the “posts” table.
Hence, we need to first check if $result['max']
is set. If it is, we return its value. Else, we return 0
. Try doing this yourself. Once this is done, the getLatestPostID()
method is complete and we can move on to the updateLastViewedPost()
method.
The updateLastViewedPost()
method is public
and has no parameters. Try declaring it yourself.
Inside the method, we need to update the “last_viewed” column of the “members” table to reflect the latest post viewed by a member. Whenever a member logs into our website, the “last_viewed” value of that member will be updated to the id of the latest post in the “posts” table.
This id is given by the getLatestPostID()
method we coded earlier. To use this method, add the following line to the updateLastViewedPost()
method:
$max = $this->getLatestPostID();
Here, we use the $this
keyword to call the getLastestPostID()
method and assign its result to $max
. Next, we need to use the queryDB()
method to execute the following SQL statement:
UPDATE members SET last_viewed = :max WHERE username = :username
To execute this statement, we need to bind the $username
property to :username
and the variable $max
to :max
. In addition, we need to decide on the appropriate mode to use when calling queryDB()
.
Try doing this yourself. Once this is done, the method is complete.
Finally, we move on to the getLastViewedPost()
method. This method is public
and has no parameters. It uses the queryDB()
method to execute the following SQL statement:
SELECT last_viewed FROM members WHERE username = :username
To execute this statement, we need to bind the $username
property to :username
and decide on the appropriate mode to use when calling the queryDB()
method. Try doing this yourself and assign the result to a variable called $result
.
Next, we need to check if $result['last_viewed']
is set. If there is no user with the username stated in the SQL query, $result['last_viewed']
will not be set.
If $result['last_viewed']
is set, we return its value. Else, we return 0
.
Try doing this yourself. Once this is done, the getLastViewedPost()
method is complete and so is the BlogMember
class.
13.5.5 Admin.php
We are left with one more class to code – the Admin
class.
The Admin
class has two private properties – $db
and $username
.
Inside the class, we have a constructor with one parameter – $pUsername
. This constructor initializes $username
with $pUsername
and $db
with a new Database
object. Try declaring the class and properties and implement the constructor yourself.
Next, we have a public
method called isValidLogin()
that has one parameter – $pPassword
. This method is very similar to the isValidLogin()
method in the BlogMember
class except that it executes the following SQL statement:
SELECT password FROM members WHERE username = :username AND is_admin = true
Try coding this method yourself.
Once that is done, we can move on to the final method – insertIntoPostDB()
.
This method is public
and has three parameters – $title
, $post
and $audience
.
Inside the method, we use the queryDB()
method to execute the following SQL statement:
INSERT INTO posts (username, title, post, audience) VALUES (:username, :title, :post, :audience)
You need to bind the $username
property to :username
and the parameters $title
, $post
and $audience
to :title
, :post
and :audience
respectively. In addition, you need to choose the correct mode for queryDB()
.
Try coding this method yourself.
Done? Great! All our classes are now complete. We are ready to move on to the process folder.
13.6 Editing The process Folder
The files in the process folder are for processing HTML forms.
13.6.1 p-index.php
We’ll start with p-index.php. This file is for processing index.php, which contains a form for members to log in to read “Members Only” posts.
index.php has two input boxes named “username” and “password” and one button named “submit”. If an error occurs when processing index.php, the page echoes an error message stored in a variable called $msg
.
If you open p-index.php (inside the process folder), you’ll see that the code has already been completed for you. This is done so that you can refer to this file when working on other processing files later. Let’s run through the code together.
First, we declare a variable called $h
and assign a new Helper
object to it. Next, we declare two variables $msg
and $username
and assign an empty string to each of them.
After declaring and initializing the variables, we are ready to process the form in index.php. We use the following if
statement to check if the “submit” button has been clicked.
if (isset($_POST['submit'])){
}
Inside the if
block, we first assign $_POST['username']
to $username
. We need to do this as we’ll be using $username
in index.php later.
Next, we use an if-else
statement to check if users have entered data into both the “username” and “password” input boxes.
To do that, we use $h
to call the isEmpty()
method in the Helper
class. This method accepts an array that contains all the variables we want to check. We pass the following array to the method:
array($username, $_POST['password'])
If any of the elements in the array is an empty string, the isEmpty()
method returns true
. When that happens, the if
block is executed and the string 'All fields are required'
is assigned to $msg
.
On the other hand, if none of the elements are empty, the else
block is executed.
Inside the else
block, we create a BlogMember
object called $member
. Next, we have another if-else
statement. This inner if-else
statement uses the $member
object to call the isValidLogin()
method in the BlogMember
class.
If the method returns false
, the condition
!$member->isValidLogin($_POST['password'])
evaluates to true
(as !false
equals true
) and the if
block is executed. The string 'Invalid Username or Password'
will then be assigned to $msg
.
On the other hand, if the method returns true
, the else
block is executed. Within this else
block, we assign $username
to a session variable called $_SESSION['username']
and use the header()
function to redirect users to read.php. Once that is done, the p-index.php page is complete.
Go through p-index.php carefully and make sure you understand it before proceeding. Got it? Great!
13.6.2 p-admin.php
Let’s move on to the p-admin.php file now. This file is for processing admin.php, which contains a form for admin to log in.
admin.php has two input boxes named “username” and “password” and one button named “submit”. If an error occurs when processing admin.php, the page echoes an error message stored in a variable called $msg
.
As you may have guessed, p-admin.php is very similar to p-index.php. Here’s what we need to do in p-admin.php:
First, declare and initialize $h
, $msg
and $username
(similar to what was done in p-index.php).
Next, check if the “submit” button has been clicked. If it has, assign $_POST['username']
to $username
.
Next, ensure that all input boxes are not empty.
If any of the boxes are empty, assign an appropriate error message to $msg
. Else, create an Admin
object and use it to call the isValidLogin()
method. (Refer to the Admin
class to find out what needs to be passed to the constructor and the isValidLogin()
method when calling them.)
If isValidLogin()
returns false
, assign an appropriate error message to $msg
to inform users that the login credentials are invalid. Else, assign $username
to $_SESSION['username']
. In addition, declare a session variable called $_SESSION['is_admin']
and assign true
to it. Finally, redirect users to write.php using the header()
function.
Got it? Try coding p-admin.php yourself. You can refer to p-index.php for reference.
Once you are done, we are ready to move on to p-signup.php.
13.6.3 p-signup.php
p-signup.php is for processing signup.php, which contains a form for members to sign up.
To keep this project short, we’ll only create a sign-up form for blog members, not for admin. We’ll learn to convert a blog member to an admin later using phpMyAdmin.
The sign-up form for blog members (signup.php) has three input boxes named “username”, “password” and “confirm_password” and one button named “submit”. If an error occurs when processing signup.php, the page echoes an error message stored in a variable called $msg
.
Here’s what we need to do in p-signup.php:
First, declare and initialize three variables $h
, $msg
and $username
(similar to what was done in p-index.php).
Next, check if the “submit” button has been clicked. If it has, we first assign $_POST['username']
to $username
. Next, we need to ensure the following:
- All input boxes in signup.php have been filled out
-
$username
has a length of between 6 and 100 characters (inclusive) -
$_POST['password']
has a length of between 8 and 20 characters (inclusive) -
$_POST['password']
contains at least one lowercase character, one uppercase character and one digit. -
$_POST['password']
and$_POST['confirm_password']
match
If any of the cases above are not met, we assign an appropriate error message to $msg
. Else, we do the following:
Create a BlogMember
object and use it to call the isDuplicateID()
method.
If the method returns true
, assign an appropriate error message to $msg
, informing users that the username is already in use. Else, use the BlogMember
object to call the insertIntoMemberDB()
method and use the header()
function to redirect users to index.php. Append the query string new=1
to index.php.
Got it? Try coding p-signup.php yourself.
Hint: Refer to p-index.php for help on processing forms. If you are not familiar with query strings, you can refer to Chapter 8.1.2.
To check the five cases listed above, you can use the isEmpty()
, isValidLength()
, isSecure()
and passwordsMatch()
methods in the Helper
class.
Refer to Helper.php and BlogMember.php to figure out what needs to be passed to the various methods in the Helper
and BlogMember
classes when calling them.
Once you are done with p-signup.php, we can move on to p-write.php. This file is for processing write.php, which contains a form for admin to write their posts.
13.6.4 p-write.php
write.php has an input box named “title”, a textarea named “post”, a drop-down list named “audience” and a button named “submit”. If an error occurs when processing write.php, the page echoes an error message stored in a variable called $msg
.
If you analyze the code in write.php, you’ll notice that we added a <script>
element after the textarea. This is for replacing the textarea with a more advanced text editor known as the CKEDITOR (available for free athttps://ckeditor.com/ckeditor-4/). We won’t go into details on how to use CKEDITOR as it only affects the user interface. Using CKEDITOR does not affect our PHP code in any way.
Here’s what we need to do in p-write.php:
First, we need to retrieve two session variables – $_SESSION['username']
and $_SESSION['is_admin']
– and check if they are set. If either of the session variables is not set, we know that the user is trying to access write.php without logging in with an admin account.
If that’s the case, we use the header()
function to redirect them to admin.php. Else, we do the following:
First, declare and initialize a Helper
object called $h
. Next, declare four variables $title
, $post
, $audience
and $msg
and assign an empty string to each of them.
After that, check if the “submit” button has been clicked.
If it has, assign $_POST['title']
, $_POST['post']
and $_POST['audience']
to $title
, $post
and $audience
respectively.
Next, check that all fields in write.php have been filled out. If there is an empty field, assign an appropriate error message to $msg
.
Else, create an Admin
object using $_SESSION['username']
as the argument. Use this object to call the insertIntoPostDB()
method. (Refer to the Admin
class to decide what arguments to pass to the method.) Once that is done, assign the string 'Message saved successfully'
to $msg
.
Clear? Try coding p-write.php yourself. Once you are done, we are ready to move on to the most complex processing file – p-read.php.
13.6.5 p-read.php
This file is for processing read.php, which is used for displaying posts to readers. We need to implement two essential features in p-read.php:
First, depending on whether the user is logged in, we need to display different posts. If the user is logged in, we display all posts. Else, we only display “Public” posts. Next, if the user is logged in, we need to display a “New” icon for posts posted to the database after the user’s last login.
To achieve the above, open p-read.php and do the following:
First, declare a variable called $h
and assign a Helper
object to it. Next, declare a variable called $update
and assign false
to it.
Last but not least, declare a variable called $is_member
and assign isset($_SESSION['username'])
to it.
Here, we use the isset()
function to check if the session variable $_SESSION['username']
is set. If it is (i.e., isset()
returns true
), we know that the reader accessing read.php is logged in.
Try doing the above yourself.
Next, we need to use a couple of if-else
statements in p-read.php. The structure is shown below:
if ($is_member){
//Create a BlogMember object and use it to call the getLastViewedPost() method
}
else{
//Create a BlogReader object
}
// Call getPostsFromDB() method
if ($posts == false){
//include the blankcard.html file
}
else{
//use a foreach loop to process the elements in $posts
//include the messagecard.php file
}
if ($is_member){
//include the logout.html file
if ($update)
//use the BlogMember object to call the updateLastViewedPost() method
}
In the first if-else
statement, we check if the user is logged in. If the user is logged in (i.e., $is_member
is true
), we initialize a BlogMember
object and assign it to a variable called $reader
. We then use $reader
to call the getLastViewedPost()
method in the BlogMember
class and assign the result to a variable called $lastPost
.
On the other hand, if the user is not logged in, we initialize a BlogReader
object and assign it to $reader
.
Try coding this if-else
statement yourself. Refer to the respective classes to find out what needs to be passed to the various methods when calling them.
Once the if-else
statement is complete, we need to use the $reader
object to call the getPostsFromDB()
method; this method is defined in the BlogReader
class.
Recall that BlogMember
is a subclass of BlogReader
? Hence, regardless of whether $reader
is a BlogMember
or BlogReader
object, it can access the getPostsFromDB()
method. Try calling this method yourself and assign the result to a variable called $posts
.
Next, we need to use a second if-else
statement to check if getPostsFromDB()
returned any results.
If there are no results (i.e., $post
is false
), we want to display the HTML code in blankcard.html (located in the output_code folder). To include this file, we use the following statement:
include "output_code/blankcard.html";
Next, inside the else
block (if there are results), we use a foreach
loop to loop through $posts
.
$posts
is a two-dimensional array fetched using the getPostsFromDB()
method in the BlogReader
class. This method uses the queryDB()
method in the Database
class, which in turn uses the built-in fetchAll()
method in the PDO
class. The fetchAll()
method fetches data from a table as a two-dimensional array, where each element in the array is an array representing a row from the table.
In our p-read.php file, we assign the data fetched from the “posts” table to $posts
. To process $posts
, we can loop through it using the following foreach
loop.
foreach($posts as $result){
$msgid = $result['id'];
$title = htmlspecialchars($result['title']);
$post = strip_tags($result['post'], "<strong><em><p><ol><ul><li><a>");
$username = htmlspecialchars($result['username']);
$postdate = htmlspecialchars($result['post_date']);
include "output_code/messagecard.php";
}
For each iteration, we assign the array in $posts
to $result
. Next, inside the loop, we assign the elements in $result
to various variables.
For instance, we assign $result['title']
to $title
. However, before we do that, we apply the htmlspecialchars()
function to $result['title']
first. The same applies to $result['username']
and $result['post_date']
.
The reason for this is we’ll be displaying the values of these elements in read.php later. Hence, we have to convert any special characters in these elements to HTML entities to prevent cross-site scripting. Refer to Chapter 8.1.6 if you have forgotten what cross-site scripting is.
However, notice that we did not apply the htmlspecialchars()
function to $result['post']
?
This is because we want to allow certain HTML tags in $result['post']
. Specifically, we want to allow the <strong>
, <em>
, <p>
, <ol>
, <ul>
, <li>
and <a>
tags.
To do that, we need to use another built-in function called strip_tags()
. This function strips a string of all HTML tags except those passed as a second argument to the function. Hence,
strip_tags($result['post'], "<strong><em><p><ol><ul><li><a>");
strips $result['post']
of all HTML tags except the ones we want. Got it?
Good! After assigning all the elements in $result
to their respective variables, we use an include
statement to include the messagecard.php file. messagecard.php is stored in the output_code folder and contains code for displaying the values of those variables above.
Once that is done, the foreach
loop is complete. Based on the code and description given above, try completing the second if-else
statement yourself.
Once you are done, we can move on to the last if
statement. This statement checks if $is_member
is true
. If it is, we use an include
statement to include the logout.html file. This file is stored in the output_code folder and contains HTML code with a logout link. In addition, we use an inner if
statement to check if $update
is true
. If it is, we use the $reader
object to call the updateLastViewedPost()
method in the BlogMember
class.
Try completing this if
statement yourself. Once that is done, the p-read.php file is complete.
Great! You have completed the hardest file in this project; we just need to tie up some loose ends now.
13.6.6 messagecard.php
First, we need to add some code to the messagecard.php file. This file is found inside the output_code folder. As mentioned above, this file contains code for displaying posts retrieved from the “posts” table.
When a user is logged in to our blog, we need to display a “New” icon in read.php for posts that were posted after the user’s last login.
As posts are displayed using the messagecard.php file, we need to make some modifications to this file.
To do that, replace the comment ( //add PHP code here
) in messagecard.php with the following if
statement:
<?php
if ($is_member and $lastPost < $msgid){
echo '<span class = "new-post">NEW</span>';
$update = true;
}
?>
Here, we use an if
statement to check if the user is logged in. In addition, we check if $lastPost
is smaller than $msgid
.
$lastPost
stores the id of the last post viewed by the user. We got that by calling the getLastViewedPost()
method in the first if-else
statement in p-read.php.
$msgid
stores the id of the current post in the foreach
loop.
If $lastPost
is smaller than the id of the current post, we know that this is a new post. Hence, we use an echo
statement to echo a <span>
tag with the word “NEW”. In addition, we set the value of $update
to true
.
$update
is used to indicate whether we need to call the updateLastViewedPost()
method (in the last if
statement in p-read.php). If $update
is true
, we call the method to update the value of the “last_viewed” column in the “members” table to the latest post id in the “posts” table.
Got it? Refer to the BlogMember
class (BlogMember.php) if you are not sure how the updateLastViewedPost()
method works.
After inserting the if
statement, we need to replace some text in messagecard.php with PHP code. Specifically, we need to replace “post_title”, “user_name”, “post_date” and “post_text” with the values of $title
, $username
, $postdate
and $post
respectively.
We do that using echo
statements. For instance, to replace “post_title”, we write
<?php echo $title; ?>
Try replacing “user_name”, “post_date” and “post_text” yourself. However, before replacing “post_date”, you need to convert $postdate
to a string. This is because $postdate
currently stores a UNIX timestamp.
To display $postdate
in a more human-readable format, you need to use the date()
function to convert the timestamp to a datetime string. Try doing this yourself, using 'd-M-Y g:i a'
as the first argument to the function. Refer to Chapter 5.2.2 if you need help with the date()
function.
Once you have updated messagecard.php, the process folder is complete.
13.7 The includes Folder
We are now ready to discuss the three remaining files in the includes folder. These three files are header.html, debugging.php and loadclasses.php.
header.html contains HTML code for the <head>
element and has already been completed for you.
debugging.php contains code for handling errors and exceptions. This file was explained in detail in Chapter 12.3. Hence, we won’t be going through it here.
However, in this project, note that we did not try to catch any exceptions. Instead, we use debugging.php to handle all exceptions. This is because it is pointless for the site to proceed when an exception occurs. For instance, if we fail to connect to the database, the rest of the site will not work. Therefore, it makes sense to simply use debugging.php to handle the exception.
If an exception or error occurs, debugging.php displays a detailed message on the browser when display_errors
is set to '1'
(i.e. when we are developing the site). When display_errors
is set to '0'
(i.e. on a live site), it logs the message and redirects users to error.html (which is stored in the main phpproject folder).
Last but not least, we have the loadclasses.php file. As the name suggests, this file is for autoloading classes. Did you notice that in all the previous files we coded, I asked you to create objects without asking you to include the relevant class files? For instance, in p-admin.php, I asked you to create a Helper
object without asking you to include Helper.php.
This will typically lead to a fatal error as each PHP script is unaware of code written in another PHP script. Hence, p-admin.php is unaware of the class defined in Helper.php. To prevent that error, we need to include Helper.php before we create a Helper
object.
However, instead of including this file ourselves, PHP provides us with a convenient alternative known as an autoloader. If we define an autoloader in our PHP script, whenever we create an object in that script, the autoloader includes the file for us automatically.
To use an autoloader, we need to ensure that the file name matches the class name. For instance, if the file name is MyClass.php, the class defined inside has to be called MyClass
.
Besides that, using an autoloader is straightforward. If you open loadclasses.php, you’ll see that we’ve defined a function called myAutoloader()
that has one parameter – $class
. Inside this function, we use an include_once
statement to include the relevant class file.
INC_DIR
is a constant that gives us a direct path to the includes folder in our project; we’ll talk more about this constant in the next section.
Inside our myAutoloader()
function, we concatenate INC_DIR
with the string 'classes/'
, the variable $class
and the string '.php'
to get a direct path to the respective class files.
For instance, if $class
equals 'Helper'
, the concatenation gives us the string
INC_DIR.'classes/Helper.php';
which is a direct path to the Helper.php file. We then use the include_once
statement to help us include this file. Got it?
After we code myAutoloader()
, we need to use a built-in function called spl_autoload_register()
to register it as the autoloader.
Once this is done, we simply need to include loadclasses.php in all our PHP scripts and PHP will autoload classes for us whenever we create an object.
That’s it! We are now ready to go back to the main phpproject folder.
13.8 Editing The phpproject Folder
Inside this folder, we have eight files, seven of which are user interface files. The seven files are signup.php, admin.php, write.php, index.php, read.php, logout.php and error.html.
Besides these user interface files, we have a file called UI_include.php.
13.8.1 UI_include.php
We’ll start with the UI_include.php file. If you open this file, you’ll see that it has already been completed for you. Let’s go through the code.
Inside the file, we first define a constant called INC_DIR
and assign the string
$_SERVER["DOCUMENT_ROOT"]. "/phpproject/includes/"
to it. $_SERVER['DOCUMENT_ROOT']
is a predefined variable that stores the document root directory under which the current script is executing. If you are using XAMPP, this root directory refers to the htdocs folder.
When we concatenate $_SERVER['DOCUMENT_ROOT']
and "/phpproject/includes/"
, we get a direct path to the includes folder.
If you rename the phpproject folder, you have to edit the path assigned to INC_DIR
accordingly. For instance, if you rename the folder to myproject, you have to assign $_SERVER["DOCUMENT_ROOT"]. "/myproject/includes/"
to INC_DIR
instead.
Next, we have two include
statements for including loadclasses.php and debugging.php. As mentioned previously, we use loadclasses.php to autoload our classes and debugging.php to handle all errors and exceptions. These two files have to be included in most of our user interface files later.
With that, the UI_include.php file is complete and we are ready to move on to the user interface files.
13.8.2 User Interface Files
The first is the error.html file. This file contains HTML code for displaying a custom message to users when an error or exception occurs on our site. It has already been completed for you.
Next, we have five very similar files – admin.php, write.php, index.php, signup.php and read.php.
Inside each file, we need to add PHP code to do the following: start a new session and include the UI_include.php and header.html files.
In addition, each of the files contains a HTML form that is processed by the file itself. To process the form, we need to use an include
statement to add the relevant processing file to the user interface file. For instance, to process the form in admin.php, we need to include the p-admin.php file.
The above has already been done for you in admin.php.
With reference to the code in admin.php, try doing the same for write.php, index.php, signup.php and read.php. In each case, note that the UI_include.php file must be included before the other two files (as we need INC_DIR
to be defined before using it). In addition, you need to change the processing file accordingly. Got it?
Great! Once you are done with the above, we need to make some modifications.
For signup.php, there is no need to start a new session as p-signup.php does not use any session variable. Hence, we should remove the session_start();
statement from signup.php.
Next, for read.php, as p-read.php includes code for displaying output, we should not include it at the start of the file. Instead, we should include it between the <body>...</body>
tags.
Last but not least, for signup.php, admin.php, index.php and write.php, we need to replace the text “Error Message Here” (without quotes) in the HTML code with the actual error message stored in a variable called $msg
. To do that, we use the PHP code below:
<?php echo $msg; ?>
Try doing all the modifications above yourself.
Now, we need to make one more modification to index.php. If you refer to p-signup.php, you’ll notice that we added the query string new=1
when directing users to index.php after a successful sign-up, we want to make use of this query string inside index.php.
To do that, add the following code to index.php after the line <div class="form">
:
<div class = "new">
<?php
if (isset($_GET['new']))
echo 'ACCOUNT CREATED SUCCESSFULLY';
?>
</div>
Here, we use the query string to check if users are directed to index.php after a successful sign-up. If yes, we echo the string 'ACCOUNT CREATED SUCCESSFULLY'
.
Got it? Great. Let’s move on to the last user interface file – logout.php. As the code for logging out is very straightforward, we did not create a separate file for processing logout.php. Instead, we’ll add the processing code to logout.php directly.
To do that, we need to do a few things in logout.php. First, we need to resume the existing session (using session_start()
) and destroy all variables in that session (using session_destroy()
). Next, we need to include the UI_include.php and header.html files.
Try doing the above yourself. Once that is done, load the pagehttp://localhost/phpproject/logout.phpin your browser; you’ll notice two identical links that say, “Click here to log in again”.
This is not a mistake. The first link points to admin.php while the second points to index.php.
If you refer back to write.php and study the code carefully, you’ll notice that we added a query string ( admin=1
) to the logout URL near the end of the page. This query string tells us that the person logging out is an admin user.
When that happens, we want to display the first logout link in logout.php. On the other hand, if the person is not an admin user (i.e., there is no query string), we want to display the second logout link. Try using an if-else
statement in logout.php to achieve the above. You can refer to index.php for help on using query strings.
Once that is done, the logout.php file is complete.
The project is almost complete at this point. In fact, if you have done everything correctly, you can loadhttp://localhost/phpproject/signup.phpand everything will work.
Try entering your desired username into the first input box and click “Submit”. What do you notice? You should get an error message that says “All fields are required”. Notice that the username you entered into the first input box is gone?
The last part of our project involves adding PHP code to your user interface files to ensure that values entered into forms are maintained if there’s an error processing the form.
Recall that we wrote a method called keepValues()
inside the Helper
class for this purpose? Suppose we have a Helper
object called $h
, here’s how we use the method:
If we have a form with a textbox named “tb” and we store the value submitted for “tb” as $tb
, we write
<input type="text" name="tb" <?php $h->keepValues($tb, 'textbox');?> >
If we have a textarea named “ta” and we store the value submitted for “ta” as $ta
, we write
<textarea name = "ta"><?php $h->keepValues($ta, 'textarea'); ?></textarea>
Finally, if we have a dropdrop list named “sl” with two value
attributes “1” and “2”, and we store the value submitted for “sl” as $sl
, we write
<select name = "sl">
<option value = '1' <?php $h->keepValues($sl, 'select', '1'); ?>>1</option>
<option value = '2' <?php $h->keepValues($sl, 'select', '2'); ?>>2</option>
</select>
In our project, the names of the variables used to store each input correspond to the names of the input fields. For instance, $username
stores the input for the textbox named “username”.
Based on the description above, modify index.php, signup.php, admin.php and write.php so that all values entered, except passwords, are maintained if there’s an error processing the form. Got it?
Once the above is done, we need to make some additional changes to write.php. For most pages, if there’s no error processing the form, the website automatically loads another page based on the URL we passed to the header()
function.
However, this does not happen for write.php. For this page, after the admin clicks on the “submit” button, he/she will stay on the same page regardless of whether data is submitted to the database successfully or not.
We want to modify write.php so that if data is inserted into the database successfully, we’ll clear the form so that the admin can write a new post. In other words, if data is inserted successfully, we do not want to call the keepValues()
method.
To do that, we need to use the variable $msg
declared in p-write.php. Recall that after data is inserted into the database, we assign the string 'Message saved successfully'
to $msg
? We can use this string to decide whether we need to call the keepValues()
method.
The example below shows how it can be done for the first input field (“title”). The underlined code shows the if
condition to use.
<input id = "txttitle" type="text" name="title" placeholder="Enter Title" autofocus <?php if ($msg != 'Message saved successfully') $h->keepValues($title, 'textbox'); ?>>
Try doing this for the other input fields.
Once that is done, we’ve completed the project. Congratulations! You are now ready to test your code to see if everything works as expected.
13.9 Running the Code
Before running the code, we need to make some changes to php.ini. Follow the instructions in Chapter 2.1 to locate php.ini and open it in Brackets. Scroll to the bottom of the page and add the following lines to it (if you have yet to do so in previous chapters):
error_reporting=E_ALL
display_errors=On
date.timezone=America/New_York
Next, we want to add one more line to php.ini. Specifically, we want to add a line to prepend debugging.php. Prepending a file means specifying that we want PHP to process this file before it processes any other PHP script.
Previously, we used the include
statement in UI_include.php to add debugging.php to our PHP scripts. This works for most errors. However, it will not work if the file that debugging.php is included in has syntax errors. If you want debugging.php to work even when there are syntax errors, you need to prepend the file. To do that, add the line
auto_prepend_file="<DOCUMENT_ROOT>/phpproject/includes/debugging.php"
to php.ini, where <DOCUMENT_ROOT>
refers to the actual path of your htdocs folder.
To find this path, loadhttp://localhost/dashboard/phpinfo.phpin your browser and search for “DOCUMENT_ROOT” (without quotes). You’ll see the path listed beside. You need to replace <DOCUMENT_ROOT>
with the path listed. For instance, if the path is /opt/lampp/htdocs
, the line should be
auto_prepend_file="/opt/lampp/htdocs/phpproject/includes/debugging.php"
The code above may appear as two lines in this book due to the limited width of the book. Do not break it into two lines, it should be written as a single line in php.ini. After prepending debugging.php, if you are using PHP 7.2 and above, you should also turn track_errors
off.
track_errors
is a built-in feature in PHP that has been deprecated since PHP 7.2. However, this feature is set to “On” by default. Leaving it on may lead to a warning that says “Directive ‘track_errors’ is deprecated found on line 0 in file Unknown” on some servers. To turn it off, simply add track_errors=Off
to the bottom of php.ini. That’s it. You can now save the file and restart Apache.
Next, go to UI_include.php and comment out the include
statement for debugging.php. As we have already prepended the file, we should not include it again.
Next, launchhttp://localhost/phpproject/signup.phpin your browser and sign up for two new accounts.
Everything works? If yes, congratulations! Give yourself a pat on the back.
If no, it’s all right. Figuring out what went wrong is a large part of programming. This, to me, is where the most learning takes place. If something fails to work, you will likely get an error message. Try to use the line number and file name given in the error message to figure out what is wrong.
Alternatively, you can use echo
statements to determine which part of your code works. For instance, if you add an echo
statement to line 10 of your code and you don’t see the output, you know that something has likely gone wrong before line 10. Similarly, if you add an echo
statement to an if
block and don’t see the output, you know that the if
block is not executed.
Try finding the error yourself. If you really can’t figure out the issue, you can check the suggested solution in the phpproject-complete folder and compare the code with your code. Copy and paste the functions that you suspect may be causing the error from the suggested solution to your solution and rerun your code to see if it works. Study the suggested solution carefully to really understand how it works. Got it?
Once you manage to find the bug and are able to sign up for two accounts successfully, you can proceed to convert one of them to an admin account. To do that, go tohttp://localhost/phpmyadmin/and click on the “project” database. Click on the SQL tab and run the following SQL statement, replacing YOUR_ADMIN_USERNAME
with the username you want to convert to an admin user:
UPDATE members SET is_admin = 1 WHERE username = 'YOUR_ADMIN_USERNAME';
Once that is done, you can use the admin account to post to the blog. To do that, go tohttp://localhost/phpproject/admin.phpto log in as an admin. Try adding some posts to the blog. Does everything work?
After posting, you can go tohttp://localhost/phpproject/index.phpto log in as a member (using either account) to read the posts.
Alternatively, you can go tohttp://localhost/phpproject/read.phpdirectly to read “Public” posts without logging in.
Play around with the site to see if everything works as expected. If something does not work, try debugging it. With perseverance, you can definitely find the error and learn a lot in the process. Have fun!