LanguagesPHPPHP 5 OOP: Delegation and Custom Exceptions

PHP 5 OOP: Delegation and Custom Exceptions

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

This article continues a series on PHP5 OOP. In the first article, PHP 5 OOP: Interfaces Abstract Classes and the Adapter Pattern, I presented an interface for abstracting database access from the actual database. In the second article, PHP 5 OOP: Protecting Data With Visibility , I showed how to expand upon the functionality built in the first article in order to show how to protect your data using visibility modifiers. In this article you will learn how delegation can mirror the query related DB interface to work on a specific query result.

The DBQuery Object

At present our DBQuery object simply mimics (all be it – rather simply) a stored procedure. Once executed a result resource is returned which you must store and pass the MySqlDB object if you 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; that are designed to work on the result of an executed query? Take the code in your previous example and let us assume that the DBQuery object now manages the result resource for us:

Listing 1: Using the DBQuery class.

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, you need to use delegation. You 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 initialized 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 behavior by sending messages to another object which implements the same or a similar behavior.

You are going to modify the DBQuery object to include all functions which work on a result resource from the DB object. You 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:

Listing 2: Extending the DBQuery class with delegation.

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 underlying database function.

Type Hinting

For delegation to work, we need to ensure that the $db variable of the DBQuery object is an instance of an object that implements the DB interface. 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 to ensure that function arguments were of a specific object type was to use the type checking functions provided PHP (i.e: is_a() ). Now you can simply enforce the object type by preceding the function argument with type name. You 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.


public function __construct(DB $db) 
{ 
    $this->db = $db; 
} 

Not only can you specify object types, you may also specify abstract classes and interfaces when using type hinting too.

Throwing Exceptions

You may have noticed from the above code that you 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 and 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.

You need to change your execute function to throw an exception if the query fails. You will throw a custom exception object called QueryException, which is passed the DBQuery object that caused the error.

Listing 3: Throwing an exception.

    /** 
     * 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 initialized, so execute 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 you 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 your own custom exception you can record extra information about the nature of the error if any, create an entry in a log, or in fact do anything you like. Your 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.

  1. A new protected property needs to be added to the class called compiledQuery.
  2. The compile() function updates the query compiledQuery property with the query text.
  3. A function to retrieve the compiled query text should be added.
  4. A function to get the current DB object associated with the DBQuery object should also be added.

Listing 4: Throwing an exception.

class DBQuery 
{ 
    /** 
     * Stored the compiled version of the query. After a call 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("/(?compile_callback($params, 1, "2")', $this->query); 

         
        return ($this->compiledQuery = $this->add_strings($query)); // put the strings back into the query 
    } 

    public function getDB() 
    { 
        return $this->db; 
    } 

    public function getCompiledQuery() 
    { 
        return $this->compiledQuery; 
    } 
} 


You can now implement the QueryException class. Note how you 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.

Listing 5: The QueryException class.

/** 
* 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 made 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 super class 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}nn"; 
         $output .= "Query: {$this->QueryText}n"; 
         $output .= "Error: {$this->ErrorMessage} ({$this->ErrorNumber})nn";             
             
         return $output; 
    } 

} 

The code seen at the beginning of this section now will now work as intended.

Conclusion

In this article you have seen how delegation can mirror the query related DB interface to work on a specific query result. The DBQuery object exposes identical functions, such as fetch_assoc() as the DB object. These however act on a single query. You have also seen how custom exceptions can be used to give detailed information on when and where an error occurred and how they enable finer control on the handling of errors. 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.

About the Author

Adam Delves is a university student and web programmer from the UK who is studying computing. He has been a PHP programmer for over 3 years and now runs two small websites and writes articles for PHP builder.com.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories