Results 1 to 7 of 7

Thread: Tutorial: PHP 5 OOP

  1. #1

    Thread Starter
    VBA Nutter visualAd's Avatar
    Join Date
    Apr 2002
    Location
    Ickenham, UK
    Posts
    4,906

    Tutorial: PHP 5 OOP

    PHP 5 OOP

    PHP 5 makes some significant improvements on the Object Orientated programming model of PHP 4, bringing it more in line with languages such as VB.NET and Java. The improved object model in PHP 5 makes developing applications using OOP much easier and gives you, the programmer greater flexibility.
    I hope to use these tutorials to introduce to you some of the new features of the PHP 5 object model as well as create something which you will find useful in the process. I have therefore chosen as a test case, a database abstraction layer, similar to the Pear DB abstraction layer, however, not as feature rich, remember this is only a tutorial.
    This thread is locked and I will add the next part of the tutorial on a regular basis, if you have any comments, criticisms, etc. about anything, please post them here.

    Contents
    • Interfaces Abstract Classes and the Adapter Pattern
    • Protecting data with Visibility
    • Using Delegation and Type Hinting
    • Making use of the Factory Pattern to make Refactoring Easier


    Introduction
    One of PHP's stronger areas is it's support for database connectivity. It is able to connect to and talk to just about any database server / interface you can imagine. However, with this comes a few inherent problems; each database system has its own features, functions and in most cases they have their own versions of SQL although the functions used to access these databases are similar they do vary subtly meaning that if you were to want to port an application written for MySql to MS SQL server, the refactoring would require for example manually changing all calls to mysql_query() to mssql_query().

    While we are not going to go as far as developing our database independent version of SQL, we shall use the new features of PHP 5 to help us build a consistent database API with support for stored procedures where it is not already present. This can then make switching from one database system to another as painless as changing one line of code.
    Last edited by visualAd; Dec 29th, 2005 at 01:48 PM.

  2. #2

    Thread Starter
    VBA Nutter visualAd's Avatar
    Join Date
    Apr 2002
    Location
    Ickenham, UK
    Posts
    4,906

    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.
    Attached Files Attached Files
    Last edited by visualAd; Apr 24th, 2006 at 12:13 AM.

  3. #3

    Thread Starter
    VBA Nutter visualAd's Avatar
    Join Date
    Apr 2002
    Location
    Ickenham, UK
    Posts
    4,906

    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.
    Last edited by visualAd; Feb 6th, 2006 at 07:53 PM.

  4. #4

    Thread Starter
    VBA Nutter visualAd's Avatar
    Join Date
    Apr 2002
    Location
    Ickenham, UK
    Posts
    4,906

    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.
    Attached Files Attached Files
    Last edited by visualAd; Feb 10th, 2006 at 08:03 PM.

  5. #5

    Thread Starter
    VBA Nutter visualAd's Avatar
    Join Date
    Apr 2002
    Location
    Ickenham, UK
    Posts
    4,906

    Re: Tutorial: PHP 5 OOP

    Using Delegation and Type Hinting

    Type Hinting is a new facility in PHP 5 which enables you to force function arguments to be objects of specific types. Before PHP 5, the only way of ensure that function arguments were of a specific object type was to use the type checking functions provided. Now we can simply enforce the object type by proceeding the function argument with its name. We have already seen type hinting in our DBQuery object, to ensure that an object which implements the DB interface is passed to the objects constructor.
    PHP Code:
        public function __construct(DB $db)
        {
            
    $this->db $db;
        } 
    Not only can we specify object types, we may also specify abstract classes and interfaces when using type hinting too.
    Delegation

    At present our DBQuery object simply mimics (all be it – rather simply) a stored procedure. Once executed a result resource is returned which we must store and pass the MySqlDB object if we wish to use functions such as num_rows() or fetch_row() on the result set. Would it not be nice if the DBQuery object were able to implement the functions which the MySqlDB object implements, which are designed to work on the result of an executed query? Take the code in our previous example and let us assume that the DBQuery object now manages the result resource for us:
    PHP Code:
        require 'mysql_db.php';
        require_once 
    'query.php';


        
    $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');

        try {
            if(
    $query->execute("visualad""apron"time()))->num_rows() == 1) {
                echo(
    'Correct Credentials');
            } else {
                echo(
    'InCorrect Credentials / Session Expired');
            }
        } catch (
    QueryException $e) {
            echo(
    'Error executing query: ' $e);
        } 
    The main lines of interest in the modified code are the catch statement and the execute statement.
    • The execute statement no longer returns a result resource, it now returns the DBQuery object itself.
    • The DBQuery object now implements the num_rows() function which we are familiar with from the DB interface.
    • If the query fails to execute, it throws an exception of type QueryException. Which when converted to a string returns details of the error which occurred.

    To implement this, we need to use delegation. We have already been using delegation in our DBQuery object, but will now use it to a greater extent to tie it in completely with the MySqlDB object. The DBQuery object is already initialised with an object which implements the DB interface and it already contains a member function called execute, which, invokes the query() method of the DB object to execute the query. The DBQuery object its self does not actually query the database, it leaves that to the DB object. This is delegation - a process by which one object implements a particular behaviour by sending messages to another object which implement the same or a similar behaviour.

    We are going to modify the DBQuery object to include all functions which work on a result resource from the DB object. We will use the result stored when the query is executed to invoke the corresponding function of the DB object and return its result. The following functions will be added:
    PHP Code:
    class DBQuery
    {
        .....

        public function 
    fetch_array()
        {
            if (! 
    is_resource($this->result)) {
                throw new 
    Exception('Query not executed.');
            }

            return 
    $this->db->fetch_array($this->result);
        }

        public function 
    fetch_row()
        {
            if (! 
    is_resource($this->result)) {
                throw new 
    Exception('Query not executed.');
            }

            return 
    $this->db->fetch_row($this->result);
        }

        public function 
    fetch_assoc()
        {
            if (! 
    is_resource($this->result)) {
                throw new 
    Exception('Query not executed.');
            }

            return 
    $this->db->fetch_assoc($this->result);
        }

        public function 
    fetch_object()
        {
            if (! 
    is_resource($this->result)) {
                throw new 
    Exception('Query not executed.');
            }

            return 
    $this->db->fetch_object($this->result);
        }

        public function 
    num_rows()
        {
            if (! 
    is_resource($this->result)) {
                throw new 
    Exception('Query not executed.');
            }

            return 
    $this->db->num_rows($this->result);
        }

    The implementation of each function is quite simple. It first checks to ensure the query has been executed, then, delegates the task to the DB object, returning its result as if it were the query object itself called the function.

  6. #6

    Thread Starter
    VBA Nutter visualAd's Avatar
    Join Date
    Apr 2002
    Location
    Ickenham, UK
    Posts
    4,906

    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.
    Attached Files Attached Files
    Last edited by visualAd; Apr 24th, 2006 at 12:14 AM.

  7. #7

    Thread Starter
    VBA Nutter visualAd's Avatar
    Join Date
    Apr 2002
    Location
    Ickenham, UK
    Posts
    4,906

    The Template Pattern

    The Template Pattern

    One of the major concepts behind the object orientated methodology is code reuse. When coding their application developers are encouraged to split large pieces of functionality up into smaller objects, each designed to accomplish a specific task. These objects should be generic enough to be used in other applications. Our MysqlDB and DBQuery object are two examples of objects which are likely to be used in more than one application.

    The Template pattern modifies the logic of a class to make it more complete or more specific. Several examples of this have already been demonstrated earlier in this tutorial through the use of inheritance. The template pattern can also be used however to make create an instance specific class, which though the use of the constructor, initialises all the required properties.
    Let us take the MySqlDB object as an example. Before any of its functions can be used, the connect() function must be called and supplied the hostname, user name and password for the server. A database also needs to be select. The connection information will often remain constant in your application, therefore an object template can be created that extends the MySqlDB object.
    PHP Code:
    class ProdMySqlB extends MySqlDB
    {
        public function 
    __construct($dbname null)
        {
            
    /* connection credentials */
            
    $host 'production.example.com';
            
    $user 'username';
            
    $password 'password';

            
    $this->connect($host$user$password); // make the connection

            
    if (! is_null($dbname)) {
                
    $dbname $this->escape_string($dbname);

                if (! 
    $this->query("use $dbname")) {
                    throw new 
    Exception("Error selecting database.");
                }           
            }
        }
    }

    class 
    SatgeMySqlB extends MySqlDB
    {
        protected 
    $log;

        public function 
    __construct($dbname null)
        {
            
    $log = new Log('dbquery.log'); // create a Log file object

            /* connection credentials */
            
    $host 'staging.example.com';
            
    $user 'username';
            
    $password 'password';

            
    $this->connect($host$user$password);

            if (! 
    is_null($dbname)) {
                
    $dbname self::escape_string($dbname);

                if (! 
    $this->query("use $dbname")) {
                    throw new 
    Exception("Error selecting database.");
                }           
            }
        }

        public function 
    query($query)
        {
            
    $this->log->write_log("Executing query: $query"); // add query text to the log

            
    return parent::query($query);
        }

    The above example creates two new subclasses of MySqDB. One for a production server (i.e: the live site) and one for a staging server, which is used to test the code before it is moved to the production server. The connection information is now contained within the class constructor which takes an optional single argument that preselects the database. You can now create a connection specific instance of either class as follows:

    PHP Code:
    $db = new ProdMySqlDB('exmaple'); // creates production connection and preselects the example database 
    What are the advantages of this and why use it?
    You can use the template pattern in this fashion has several advantages:
    • Creating instance specific classes for any object in your application means that should the initialisation information (i.e: the connection information in the above example) were to change. It need only be changed in one place.
    • As it is an implementation of inheritance, the template pattern enables you to extend the functionality of the object and add extra features. The SatgeMySqlB, creates an instance of a Log file object (not defined here) and writes each query executed to the log. Useful on a staging server where the need to debug code and database queries often arises.
    • Lastly, through the use of the template pattern you can create several versions of the same object with different instance specific initialisation data, as demonstrated above. A factory function can then be defined which selects the most appropriate object based on the current environment:
      PHP Code:
      function create_db()
      {
          
      $ip $_SERVER['SERVER_ADDR']; // get the servers IP address

          
      switch($ip) {
              case 
      '10.10.1.5'// staging server
                  
      return new StageMySqlDB('example');
              default: 
      // all other IP's signify this is the live site
                  
      return new ProdMySqlDB('example');
          }



    Creating an instance of the MySqlDB object and connecting to the database is now as simplae as:
    PHP Code:
    $db create_db(); // executes the factory function 
    Conclusion

    In this series of articles I have taken you through new features provided by the PHP 5 object model and demonstrated how design patterns (methods of solving common OOP related problems) can be applied through PHP. You should now be equipped with the knowledge needed to effectively use PHP 5's new object model. In using it effectively, you will produce more efficient, scalable and robust applications, while making the job of coding it easier.


    If you have any comments regarding this tutorial please post them here, not in this thread.
    PHP || MySql || Apache || Get Firefox || OpenOffice.org || Click || Slap ILMV || 1337 c0d || GotoMyPc For FREE! Part 1, Part 2

    | PHP Session --> Database Handler * Custom Error Handler * Installing PHP * HTML Form Handler * PHP 5 OOP * Using XML * Ajax * Xslt | VB6 Winsock - HTTP POST / GET * Winsock - HTTP File Upload

    Latest quote: crptcblade - VB6 executables can't be decompiled, only disassembled. And the disassembled code is even less useful than I am.

    Random VisualAd: Blog - Latest Post: When the Internet becomes Electricity!!


    Spread happiness and joy. Rate good posts.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width