Lesson 4: Optimizing the Code with Classes and Objects
In this lesson you optimize the code to facilitate maintaining it in the future. This affects the files createNewWisher.php and wishlist.php. Additionally, a new file called db.php is created.
Your application's code contains several similar blocks
with queries to the database. To make the code easier to read and maintain in the future,
you can extract these blocks, implement them as functions of a separate class called WishDB, and place WishDB in db.php. Afterwards you can
include the db.php file in any PHP file and use any function from WishDB without code duplication. Such an approach ensures that any changes to queries or functions will be made in one place and you will not have to parse the entire application code.
When you use a function from WishDB, you do not change the value of any of WishDB's variables. Instead, you use the WishDB class as a blueprint for creating an object of WishDB, and you change the values of variables in that object. When you finish working with that object, it is destroyed. Because the values of the WishDB class itself are never changed, you can reuse the class an unlimited number of times. In some cases you may want to have multiple instances of a class in existance at the same time, and in other cases you may prefer a "singleton" class, where you only have one instance in existance at any one time. WishDB in this tutorial is a singleton class.
Note that the term for creating an object of a class is "instantiating" that class, and that another word for an object is an "instance" of a class. The general term for programming with classes and objects is "object-oriented programming," or OOP. PHP 5 uses a sophisticated OOP model. See php.net for more information.
The current document is a part of the Creating a CRUD Application in the NetBeans IDE for PHP tutorial.
Click here to download the source code that reflects the project state after the previous lesson is completed.
Creating the db.php File
Create a new subfolder in the Source Files folder. Name the folder Includes. Create a new file named db.php and place it in Includes. Later you can add more files to this folder that
will be included in other PHP files.
To create db.php in a new folder:
Click the right mouse button on the Source files node and choose New > Folder from the context menu. The New Folder dialog opens.
In the Folder Name field, type Includes. Then click Finish.
Click the right mouse button on the Includes node and choose New > PHP File from the context menu. The New PHP File dialog opens.
In the File Name field, type db. Then click Finish.
Creating the WishDB Class
To create the WishDB class, you need to initialize the variables of the class and implement a constructor of the class.
Open the file db.php and type the following code in it:
<?php
class WishDB {
// single instance of self shared among all instances
private static $instance = null;
The object (or "instance") of the WishDB class is stored in the $instance variable. In the code above, it is declared null. You also declare database configuration variables for storing the name and password of the database owner (user), the name of the database, and the database host. All these variable declarations are "private," meaning that the initial values in the declarations cannot be accessed from outside the WishDB class (See php.net).
Instantiating the WishDB class
For other PHP files to use functions in the WishDB class, these PHP files need to call a function that creates an object of ("instantiates") the WishDB class. WishDB is designed as a singleton class, meaning that only one instance of the class is in existance at any one time. It is therefore useful to prevent any external instantiation of WishDB, which could create duplicate instances.
Inside the WishDB class, type or paste the following code:
//This method must be static, and must return an instance of the object if the object
//does not already exist.
public static function getInstance() {
if (!self::$instance instanceof self) {
self::$instance = new self;
}
return self::$instance;
}
// The clone and wakeup methods prevents external instantiation of copies of the Singleton class,
// thus eliminating the possibility of duplicate objects.
public function __clone() {
trigger_error('Clone is not allowed.', E_USER_ERROR);
}
public function __wakeup() {
trigger_error('Deserializing is not allowed.', E_USER_ERROR);
}
The getInstance function is "public" and "static." "Public" means that it can be freely accessed from outside the class. "Static" means that the function is available even when the class has not been instantiated. As the getInstance function is called to instantiate the class, it must be static.
The double-colon (::), called the Scope Resolution Operator, and the self keyword are used to access static functions. Self is used from within the class definition to refer to the class itself. When the double-colon is used from outside the class definition, the name of the class is used instead of self. See php.net on the Scope Resolution Operator.
Adding a Constructor to the WishDB Class
A class can contain a special method known as a 'constructor' which is automatically processed whenever an instance of that class is created. In this tutorial, you add a constructor to WishDB that connects to the database whenever WishDB is instantiated.
Add the following code to WishDB:
// private constructor
private function __construct() {
$this->con = mysql_connect($this->dbHost, $this->user, $this->pass)
or die ("Could not connect to db: " . mysql_error());
mysql_query("SET NAMES 'utf8'");
mysql_select_db($this->dbName, $this->con)
or die ("Could not select db: " . mysql_error());
}
Note the use of the pseudovariable $this instead of the variables $con, $dbHost, $user, or $pass. The pseudovariable $this is used when a method is called from within an object context. It refers to the value of a variable within that object.
Functions in the WishDB Class
In this lesson you will implement the following functions of the WishDB class:
create_wisher for adding a new wisher record to the table wishers
Function get_wisher_id_by_name
The function requires the name of a wisher as the input parameter and returns the wisher's id.
Type or paste the following function into the WishDB class, after the WishDB function:
public function get_wisher_id_by_name ($name) {
$name = mysql_real_escape_string($name); $result = mysql_query("SELECT id FROM wishers WHERE name = '" . $name . "'"); if (mysql_num_rows($result) > 0) return mysql_result($result, 0); else return null;
}
The code block executes the query
"SELECT ID FROM wishers WHERE Name = '" . $name . "'",
where $name is the name of the wisher.
The query result is an array of id's from the records that meet the query. If the array is not empty this automatically means that it contains one element because the field name is specified as UNIQUE during the table creation. In this case the function returns the first element of the $result array (the element with the zero numbered).
If the array is empty the function returns null.
Security Note: The $name string is escaped in order to prevent SQL injection attacks. See Wikipedia on SQL injections and the mysql_real_escape_string documentation. Although in the context of this tutorial you are not at risk of harmful SQL injections, it is best practice to escape strings in MySQL queries that would be at risk of such an attack.
Function get_wishes_by_wisher_id
The function requires the id of a wisher as the input parameter and returns the wishes registered for the wisher.
Enter the following code block:
public function get_wishes_by_wisher_id($id) { return mysql_query("SELECT * FROM wishes WHERE wisher_id=" . $id); }
The code block executes the query "SELECT * FROM wishes WHERE wisher_id=" . $id and returns a resultset which is an array of records that meet the query. The selection is performed by the wisher_id, which is the foreign key for the table wishes.
Function create_wisher
The function creates a new record in the wishers table. The function requires the name and password of a new wisher as the input parameters and does not return any data.
Enter the following code block:
public function create_wisher ($name, $password){
$name = mysql_real_escape_string($name); $password = mysql_real_escape_string($password); mysql_query("INSERT INTO wishers (name, password) VALUES ('" . $name . "', '" . $password . "')");
}
The code block executes the query
"INSERT wishers (Name, Password) VALUES ('".$name."', '".$password."')",
where $name is the name of the new wisher and $password is the wisher's password.
The query adds a new record to the "wishers" table with the fields "name" and "password" filled in with the values of $name and $password respectively.
Refactoring Your Application Code
Now that you have a separate class for working with the database, you can replace duplicated blocks with calls to the relevant functions from this class. This will help avoid misspelling and inconsistency in the future. Code optimization that does not affect the functionality is called refactoring.
Refactoring the wishlist.php File
Start with the wishlist.php file because it is short and the improvements will be more illustrative.
At the top of the <?php ?> block, enter the following line to enable the use of the db.php file:
require_once("Includes/db.php");
Replace the following code block:
$con = mysql_connect("localhost", "phpuser", "!phpuser"); if (!$con) { die('Could not connect: ' . mysql_error()); } mysql_query("SET NAMES 'utf8'"); mysql_select_db("wishlist", $con); $wisher = mysql_query("SELECT ID FROM wishers WHERE Name='".mysql_real_escape_string($_GET["user"])."'"); $wisherID = mysql_result($wisher, 0);
This code first calls the getInstance function in WishDB. getInstance returns an instance of WishDB, and the code calls the get_wisher_id_by_name function within that instance.
No code is necessary here for opening a connection to the database. The connection is opened by the constructor of the WishDB class. If the name and/or password changes, you need to update only the relevant variables of the WishDB class.
Replace the following code:
$result = mysql_query("SELECT * FROM wishes WHERE Wisherid=". $wisherID);
with a call of the function get_wishes_by_wisher_id:
The code is not necessary because the connection to the database is automatically closed as soon as the WishDB object is destroyed.
Refactoring the createNewWisher.php File
Refactoring will not affect the HTML input form or the code for displaying the related error messages.
At the top of the <?php?> block, enter the following code to enable the use of the db.php file:
require_once("Includes/db.php");
Replace the following code block:
$con = mysql_connect($dbHost, $dbUsername, $dbPasswd);
if (!$con) {
die('Could not connect: ' . mysql_error());
}
mysql_query("SET NAMES 'utf8'");
mysql_select_db("wishlist", $con);
$wisher = mysql_query("SELECT id FROM wishers WHERE name='".$_POST["user"]."'");
$wisherIDnum=mysql_num_rows($wisher);
if ($wisherIDnum) {
$userNameIsUnique = false;
}
with code that calls that gets an instance of WishDB and calls the get_wisher_id_by_name function from that instance:
$wisherID = WishDB::getInstance()->get_wisher_id_by_name($_POST["user"]); if ($wisherID) { $userNameIsUnique = false; }
The WishDB object exists as long as the current page is being processed. It is destroyed after the processing is completed or interrupted. The code for opening a connection to the database is not necessary because this is done by the WishDB function. The code for closing the connection is not necessary because the connection is closed as soon as the WishDB object is destroyed.
To send comments and suggestions, get support, and keep informed on the latest
developments on the NetBeans IDE PHP development features, join
the mailing list.