2 Attachment(s)
Interfaces Abstract Classes and the Adapter Pattern
Interfaces Abstract Classes and the Adapter Pattern
The first feature we will visit which is new to PHP 5, is abstract classes and interfaces. These concepts are nothing more than features added to OOP which force the programmer to follow good coding standards.
Abstract Classes
An abstract class is a class which is only partially implemented by the programmer. It may contain 1 or more abstract methods. An abstract method is simply a function definition and serves to tell the programmer that the method must be implemented in a child class. To create an abstract class we use the following:
PHP Code:
<?php
abstract class Weapon
{
private $SerialNumber;
abstract public function fire();
public function __construct($SerialNumber)
{
$this->SerialNumber = $SerialNumber;
}
public function getSerialNumber()
{
return $this->SerialNumber;
}
}
?>
The above abstract class contains some of the methods required for a weapon, but, the fire method cannot be implemented because each different weapon fires using a different mechanism. We therefore declare this as abstract. Because the class is abstract an instance of it can never be created (remember, it is only a partial implementation), instead we must create a child class using inheritance and implement the fire method in the subclass, failure to do so will result in a fatal error.
PHP Code:
<?php
class Gun extends Weapon
{
public function fire()
{
if($this->SafetyOff) {
return $this->CurrentBullet;
}
}
}
class Cannon extends Weapon
{
public function fire()
{
$this->NeedsLoading = true;
return $this->CurrentCanon;
}
?>
We can now create an instance of the Cannon and Gun classes because they now fully implemented subclasses of weapon.
Interfaces
An interface is similar to an abstract class, indeed the occupy the same namespace as classes and abstract classes, (hence you cannot define an interface with the same name as a class). An interface however, is a fully abstract class; none of its method are implemented and, instead of a class subclassing from it; it is said to implement that interface.
We are going to use an interface in our database abstraction layer, to ensure that every time we create a class for a particular database, it exposes the same API. When using them, we can then rely on the methods defined in the interface being part of the class, because, if they are not, PHP will not parse it.
We are going to take the MySql functions as an example as it is the most commonly used database amongst PHP programmers. The most commonly used functions are:
mysql_connect()
mysql_error()
mysql_errno()
mysql_query()
mysql_fetch_array()
mysql_fetch_row()
mysql_fetch_assoc()
mysql_fetch_object()
mysql_num_rows()
mysql_close()
If all our database classes expose the same methods with the same return types we can be sure that changing from MySql to Postgre SQL will be painless. We therefore arrive at the following interface:
PHP Code:
interface DB
{
public function connect();
public function error();
public function errno();
public static function escape_string($string);
public function query($query);
public function fetch_array($result);
public function fetch_row($result);
public function fetch_assoc($result);
public function fetch_object($result);
public function num_rows($result);
public function close();
}
Each method declared in the interface must be defined by any class which implements it having at least the parameters identified. It may have more parameters (as long as they are optional), but it cannot have less. So let us look at a class which implements the DB interface. Didn't I mention something called the adapter pattern in the title? Yes, I did. Well, this is it – the adapter pattern is used by programmers in order to adapter one API to another using the original API. The API we are adapting from could be another object based API or like we are doing an adaption from a modular API. If you want to read more about the adapter pattern, you can find a more detailed explanation here.
Notice how we have included an escape_string() method as a static method. This method does not require an active connection to a database and should not require and instance of any object which implements the DB interface. In my opinion, this is the single most important method of any database implementation; an poorly implemented escape string method could make your applications vulnerable SQL injection.
PHP Code:
class MySqlDB implements DB
{
private $link;
public function connect($server='', $username='', $password='', $new_link=true, $client_flags=0)
{
$this->link = mysql_connect($server, $username, $password, $new_link, $client_flags);
}
public function errno()
{
return mysql_errno($this->link);
}
public function error()
{
return mysql_error($this->link);
}
public static function escape_string($string)
{
return mysql_real_escape_string($string);
}
public function query($query)
{
return mysql_query($query, $this->link);
}
public function fetch_array($result, $array_type = MYSQL_BOTH)
{
return mysql_fetch_array($result, $array_type);
}
public function fetch_row($result)
{
return mysql_fetch_row($result);
}
public function fetch_assoc($result)
{
return mysql_fetch_assoc($result);
}
public function fetch_object($result)
{
return mysql_fetch_object($result);
}
public function num_rows($result)
{
return mysql_num_rows($result);
}
public function close()
{
return mysql_close($this->link);
}
You'll notice that there are many more mysql functions than we have adapted. However, this small subset of functions are sufficient to meet the needs of most applications which require trivial data storage an retrieval. The additional functions can be implemented and I have done this in the example file attached, you may also choose to add additional functionality to the class and the interface.
PHP Code:
$db = new MySqlDb;
$db->connect('host', 'username', 'password');
$db->query('use users'); // we could also use $db->select_db here but it is not consistent with the interface
$result = $db->query("SELECT username FROM users");
while($row = $db->fetch_assoc($reuslt)) {
echo($row['username']);
}
You can now create a class for each database you want and as long as it implements the DB interface, switching from one to another is as easy as changing one line of code:
PHP Code:
$db = new MsSqlDb;
Conclusion
In this article you have seen how to protect and restrict the visibility of object data using the accessibility modifiers private, protected and public when declaring class variables. These modifiers are used in the MysqlDB and DBQuery classes to protect data important to the internal workings of the objects.
In our next article I'll present another new feature in PHP 5, type hinting. You will also get to extend the DBQuery to allow for support of all the functions in the DB interface that operate on the result of an executed query. This will be accomplished using delegation.
If you have any comments regarding this tutorial please post them here, not in this thread.
Protecting data with Visibility
Protecting Data With Visibility
You may have noticed in the previous example the use of the $link variable of the MySqlDB class. This variable is used to store the link resource generated when a connection is made to the MYSql database. You may also have noticed the word private before its declaration in contrast to the PHP 4 method of prefixing the variable using var. The word private refers to the visibility (also known as accessibility) of the variable within the class. Visibility is similar to variable scope, however, offers finer control. There are three types of visibility.
- Public (default) – the variable can be accessed and changed globally by anything.
- Protected – the variable can be accessed and changed only by direct descendants (those who inherit it using the extends statement) of the class and class its self
- Private – the variable can be accessed and changed only from within the class.
Similar to the implementation of interfaces, attempting to violate these rules in your program will generate a fatal error and, again, like interfaces, their existence is purely for the convenience of the programmer; this does not mean however they should be ignored. Specifying the visibility of your class member variables enables you to protect data within your objects from outside influence. The $link variable in the MySqlDB class is declared as private – this means that the variable can only be accessed from within the object using the $this variable preventing it from being overwritten accidentally by any other object or function outside the class.
We will use the visibility tools to help us create a query object for our database abstraction layer.
We can think of a query as an individual entity with text which is executed and a result aquired after its execution. It would be useful to keep these two attributes of a query in the same place, an object perhaps?? Some database systems also have a facility which provides stored procedures. Stored procedures are similar to functions; they are stored queries which can take parameters. Versions of MySql before 5.1 and some of database management systems do not however provide this feature.
We will incorporate both of these features into our query object. It will emulate a basic stored procedure and store the result pointer internally. We will build on the query object in the next part of the article to tie it in properly with our database abstraction layer. For now we will work on just the query object, which we will simply call query the query() function of an object which implements the DB interface.
We will define the following public functions in our query object:
- __construct() - the constructor will take an argument containing the a reference to an instance of an object which implements the DB interface.
- prepare() - the prepare function initialises the stored procedure functionality for the query. It may contain one or more numbered place holders which are then passed as arguments to the execute() function. The place holders are defined using a colon followed by an integer pertaining to the argument number and a letter pertaining to the data type of the argument.
A simple query containing a place holder may look like this:
Code:
SELECT col1,col2 FROM table_name WHERE col1=:1I
- execute() - the execute function executes the query. If it was initialised previously as a stored procedure using the prepare() function, any parameters are passed as arguments to the stored procedure. Otherwise the first argument only is used as the query text. The execute function returns the result of the executed query.
- compile() - the compile function is similar to the execute function, however, the query is not actually executed. Instead it takes the arguments of the stored procedure and returns the compiled version of the query with all the place holders substituted.
Protected Members
As mentioned above we can use the concept of visibility to hide the internal workings of our query object and protect the integrity of data required for its internal workings. We have already explained that we will be storing the result pointer returned by the query as a protected property. We use protected members, because a database specific query object which extends from our query object may need to override the core functionality.
If you have any comments regarding this tutorial please post them here, not in this thread.
1 Attachment(s)
Protecting data with Visibility
Can I Hve Cod Pls!!!!
Enough theory, lets get coding. First we will create a template for our object and build from that.
PHP Code:
class DBQuery
{
/**
* Holds a reference to an object which implements the DB interface.
*/
protected $db;
/**
* Set to true if this is a stored procedure.
*/
protected $stored_procedure = false;
/**
* Holds a query with all the strings removed.
*/
private $query;
/**
* Used to match quotes in SQL
*/
private static $QUOTE_MATCH = "/(\".*(?<!\\\)\")|('.*(?<!\\\)')/Ue";
/**
* An array containing all the strings, if any, of the query.
*/
protected $query_strings = array();
protected $result;
/**
* Class Constructor
* @param DB $db Reference to an object which implements the DB interface.
*/
public function __construct(DB $db)
{
$this->db = $db;
}
public function prepare($query)
{
$this->stored_procedure = true;
}
public function compile($args)
{
}
public function execute($query)
{
}
}
The Prepare Function
We will build the prepare() function first. To ensure that no characters within quotes are accidentally parsed as place holders, we remove all strings and store them temporarily in an array. The strings themselves are also replaced with place holders identified by a sequence of characters which should never appear in an SQL statement. During compilation of the query, the procedure placeholders are first substituted, then the strings of put back into the query. We accomplish this with the help of the preg_replace() function and a helper, function, used as a callback of the preg_replace() function.
PHP Code:
/**
* Prepares the query as a stored procedure.
* @param string $query Prepared query text
* @return void
*/
public function prepare($query)
{
$this->stored_procedure = true;
$this->quote_store = array(); // clear the quote store
$this->query = preg_replace(self::$QUOTE_MATCH, '$this->sql_quote_replace("\1"?"\1":\'\2\')', $query);
}
private function sql_quote_replace($match)
{
$number = count($this->query_strings);
$this->query_strings[] = $match;
return "$||$$number";
}
Notice the use of the private visibility for the static QUOTE_MATCH property, the quote_store property and the sql_quote_replace() function. We want these to be private rather than protected to ensure that any subclass which overrides the query class prepare() method uses its own mechanisms to exclude quotes.
The Compile Function
Next task is to build the compile() and execute() functions.
The compile() function will:
- Take a variable number of arguments; which, match the place holders in the query.
- Check that the place holder is of the correct data type and substitute it for the value in the argument.
- Return the query as a string but does not execute it.
- Throw an exception if the query object has not been initialised as a stored procedure using the prepare() function.
PHP Code:
/**
* Returns the compiled query without executing it.
* @param mixed $args,... Query Parameters
* @return string Compiled Query
*/
public function compile($params)
{
if (! $this->stored_procedure) {
throw new Exception("Stored procedure has not been initialized.");
}
/* substitute parameters */
$params = func_get_args(); // get function arguments
$query = preg_replace("/(?<!\\\\)\:(\d+)([SIN])/e", '$this->compile_callback($params, \1, "\2")', $this->query);
return $this->add_strings($query); // put the strings back into the query
}
/**
* Re-inserts strings removed by the prepare() function.
*/
private function add_strings($string)
{
$numbers = array_keys($this->query_strings);
$count = count($numbers);
$searches = array();
for($x = 0; $x < $count; $x++) {
$searches[$x] = "$||\${$numbers[$x]}";
}
return str_replace($searches, $this->query_strings, $string);
}
/**
* Executed each time a place holder is substituted in the stored procedure.
*/
protected function compile_callback($params, $index, $type)
{
--$index;
/* throw an exception */
if (! isset($params[$index])) {
throw new Exception("Required number of arguments not sent to stored procedure.");
}
/* you may want to add additional types such as dates and times here */
switch ($type) {
case 'S':
return '"' . $this->db->escape_string($params[$index]) . '"';
break;
case 'I':
return (int) $params[$index];
break;
case 'N':
return (float) $params[$index];
default:
throw new Exception("Unrecognised data type '$type' specified in stored procedure.");
}
}
The compile() function uses two additional functions. The compile_callback() function is used as a callback in the preg_replace() function call, it is executed each time a place holder is found in the query; replacing the place holder with the value in passed to the compile function.
The Execute Function
Finally, the execute() function. The execute() function compiles the query and then executes it using the DB object which was used to initialise the DBQuery object, notice how the call_user_func_array() function is used to get the compiled query:
PHP Code:
/**
* Executes the current Query
*
* Executes the current query replacing any place holders with the supplied
* parameters.
*
* @param mixed $queryParams,... Query parameter
* @return resource A reference to the resource representing the executed query.
*/
public function execute($queryParams = '')
{
//example: SELECT * FROM table WHERE name=:1S AND type=:2I AND level=:3N
$args = func_get_args();
if ($this->stored_procedure) {
/* call the compile function to get the query */
$query = call_user_func_array(array($this, 'compile'), $args);
} else {
/* a stored procedure was not initialised, so executre this as a standard query */
$query = $queryParams;
}
$this->result = $this->db->query($query);
return $this->result;
}
The complete source for the query object may be found as an attachment to this post. Remember that we are reinventing a very simplistic version of the wheel here, as stored procedures and database abstraction has already been addressed by the PearDB PHP extension.
A short Example
To demonstrate how the query object may be used, i have constructed the a small example which uses the DBQuery object as a stored procedure in checking for a correct user name and password.
PHP Code:
require 'mysql_db.php5';
require_once 'query2.php5';
$db = new MySqlDb;
$db->connect('host', 'username', 'pass');
$db->query('use content_management_system');
$query = new DBQuery($db);
$query->prepare('SELECT fname,sname FROM users WHERE username=:1S AND pword=:2S AND expire_time<:3I');
if ($result = $query->execute("visualad", "apron", time())) {
if ($db->num_rows($result) == 1) {
echo('Correct Credentials');
} else {
echo('InCorrect Credentials / Session Expired');
}
} else {
echo('Error executing query: ' . $db->error());
}
Next Time: We will have a look at another new feature in PHP 5, type hinting. We will also extend the DBQuery allowing it to support all the functions in the DB interface which operate on the result of an executed query. This will be accomplished using delegation.
If you have any comments regarding this tutorial please post them here, not in this thread.
2 Attachment(s)
Using Deleation and Type Hinting
Throwing Exceptions
You may haven noticed from the above code that we are catching an exception called, QueryException (we will implement this object later). An exception is similar to an error however more generic. The best way to describe an exception is an emergency. While an emergency might not necessarily be fatal, it must be dealt with. When an exception is thrown in PHP, the current scope of execution is immediately terminated, whether it be a function, try..catch block or the script itself. The exception then travels up the calling stack terminating each execution scope until it is either caught in try..catch block or it reaches the top of the calling stack where it will generate a fatal error.
Exception handling is another new feature in PHP 5 which, when used in conjunction with OOP, allows for fine control over error handling an reporting. A try..catch block acts as a mechanism to deal with an exception. Once caught, execution of the script continues from the next line of the scope from which the exception was caught and handled.
We need to change our execute function to throw an exception, if the query fails. We will throw a custom exception object called, QueryException, which is passed the DBQuery object that caused the error.
PHP Code:
/**
* Executes the current Query
*
* Executes the current query replacing any place holders with the supplied
* parameters.
*
* @param mixed $queryParams,... Query parameter
* @return resource A reference to the resource representing the executed query.
*/
public function execute($queryParams = '')
{
//example: SELECT * FROM table WHERE name=:1S AND type=:2I AND level=:3N
$args = func_get_args();
if ($this->stored_procedure) {
/* call the compile function to get the query */
$query = call_user_func_array(array($this, 'compile'), $args);
} else {
/* a stored procedure was not initialised, so executre this as a standard query */
$query = $queryParams;
}
$result = $this->db->query($query);
if (! $result) {
throw new QueryException($this);
}
$this->result = $result;
/* notice how we now return the object itself, this enables us to us
to call member function from the return result of this function */
return $this;
}
Using Inheritance to Throw Custom Exceptions
In PHP we can throw any object as an exception, but as a rule of thumb the exception should extend PHP's built in exception class. By creating our own customer exception, we can record extra information about the nature of the error if any, create an entry in a log, in fact, you can do anything you like. Our custom exception will do several things:
- record the error message from the DB object generated by the query
- give the exact details about the line on which the query error occurred by examining the calling stack.
- display the error message and query text when converted to a string
In order to get error information and the query text, several changes need to be made to the DBQuery object.
- A new protected property has been added to the class called compiledQuery
- The compile() function updates the query compiledQuery property with the query text.
- A function to retrieve the compiled query text has been added.
- A function to get the current DB object associated with the DBQuery object has also been added.
PHP Code:
class DBQuery
{
/**
* Stored the compiled version of the query. After a calle to compile() or execute()
*
* @var string $compiledQuery
*/
protected $compiledQuery;
/**
* Returns the compiled query without executing it.
* @param mixed $params,... Query Parameters
* @return string Compiled Query
*/
public function compile($params='')
{
if (! $this->stored_procedure) {
throw new Exception("Stored procedure has not been initialized.");
}
/* substitute parameters */
$params = func_get_args(); // get function arguments
$query = preg_replace("/(?<!\\\\)\:(\d+)([SIN])/e", '$this->compile_callback($params, \1, "\2")', $this->query);
return ($this->compiledQuery = $this->add_strings($query)); // put the strigns back into the query
}
public function getDB()
{
return $this->db;
}
public function getCompiledQuery()
{
return $this->compiledQuery;
}
}
We can now implement the QueryException class. Note how we traverse the calling stack to find the actual location in the script which caused the error. This comes into play when the DBQuery object which threw the exception is a descendant which is inherited from the DBQuery object.
PHP Code:
/**
* Query Exception
*
* Thrown by the {@link DBQuery} object if an error occurs while
* attempting to execute a query.
*
*/
class QueryException extends Exception
{
/**
* Query Text
*
* @var string $QueryText;
*/
protected $QueryText;
/**
* Error Number / Code from the Database
*
* @var string $ErrorCode
*/
protected $ErrorNumber;
/**
* Error Message from the Database
*
* @var string $ErrorMessage
*/
protected $ErrorMessage;
/**
* Class constructor
*
* @param DBQuery $db Query object which threw this exception.
*/
public function __construct(DBQuery $query)
{
/* get the calling stack */
$backtrace = $this->GetTrace();
/* set the line and file - to the location where the error actually occurred */
if (count($backtrace) > 0) {
$x = 1;
/* if the query class was inherited we need to disregard the calls by the classes descendants */
while((! isset($backtrace[$x]['line'])) ||
(isset($backtrace[$x]['class']) && is_subclass_of($backtrace[$x]['class'], 'DBQuery')) ||
(strpos(strtolower(@$backtrace[$x]['function']), 'call_user_func')) !== false ) {
/* loop while there is no line number or the function called is a descendant of the DBQuery class */
++$x;
/* if we reach the end of the stack, we use the first caller */
if (($x) >= count($backtrace)) {
$x = count($backtrace);
break;
}
}
/* if the above loop mader at least on iteration, we reduce it by 1 to find the actual line of code
which caused the error */
if ($x != 1) {
$x -= 1;
}
/* finally we can set the file and line numbers, which should reflect the SQL statement which caused the error */
$this->line = $backtrace[$x]['line'];
$this->file = $backtrace[$x]['file'];
}
$this->QueryText = $query->getCompiledQuery();
$this->ErrorNumber = $query->getDB()->errno();
$this->ErrorMessage = $query->getDB()->error();
/* call the superclass Exception constructor */
parent::__construct('Query Error', 0);
}
/**
* Get Query Text
*
* @return string Query Text
*/
public function GetQueryText()
{
return $this->QueryText;
}
/**
* Get Error Number
*
* @return string Error Number
*/
public function GetErrorNumber()
{
return $this->ErrorNumber;
}
/**
* Get Error Message
*
* @return string Error Message
*/
public function GetErrorMessage()
{
return $this->ErrorMessage;
}
/**
* Called when the object is casted to a string.
* @return string
*/
public function __toString()
{
$output = "Query Error in {$this->file} on line {$this->line}\n\n";
$output .= "Query: {$this->QueryText}\n";
$output .= "Error: {$this->ErrorMessage} ({$this->ErrorNumber})\n\n";
return $output;
}
}
The code seen at the beginning of this section now will now work. Through the use of the adaptor pattern, polymorphism, delegation and with a little help from PHP's new object model, we have developed a robust database access API which can be applied to any database. The updated DBQuery object and the QueryException object code are attached to this post. Next time we will look at how we can use inheritance in conjunction with the template pattern to create application specific instances of our objects.
If you have any comments regarding this tutorial please post them here, not in this thread.