Page 1 of 1

[HINT] Better Exceptions!

Posted: Mon May 07, 2012 1:42 am
by Weiry
I am not sure what sort of tag this really falls under, but this may be useful to those who are out using Exceptions.

If you haven't done so during your OO programming, i would recommend using a custom Exception class.
For the main reason, you can customise the __toString() so that you can output your own custom Exception text.

Now the __toString() is good for beginners to learn how to extend, but i discovered that there was not an easy way (at first glance) to handle debugging in some situations.

Say you have a database class which you include into many of your php class files so that you can access everything easily.
Now that is all well and good, but have you ever tried to add Exception's into that by doing something similar to the following?

Code: Select all

class dbConnector{
  public function query($query, $_return = false){
	
	try{

	    if($_return){

		return mysql_query($query, $this->link);

	    }

	    $this->result = mysql_query($query, $this->link);
	    
	    if(!$this->result){
		
		throw new Exception ( sprintf("MySQL.Error: %s", mysql_error()));
		
	    }
	    
	    return true;
	    
	}catch (Exception $e){
	    
	    die( $e );
	    
	}
  }
}
Now this way is pretty good to begin with, because your actually handling your error's and not just immediately killing the script.
If you have ever tried to debug a class which extends (in this case) dbConnector and you get and error in your query such as "MySQL.Error: Table 'mydatabase.mytable' doesn't exist"
And then thought to yourself, "Geez, where was that query that caused that error"

You may or may not have thought to try and use __CLASS__, __FILE__ with __LINE__ to try and find the place where the problem occurred.
Well this is all well and good although do you really want to worry about writing any of those magic constants every time you have an error just to make sure you know where the problem lies?

Well Exceptions have a nifty function called getTrace() which returns an array of debug trace results which happens to include the class names and line numbers for each function call it passes through.
Extending Exceptions

Code: Select all

    final public  function getMessage();        // message of exception
    final public  function getCode();           // code of exception
    final public  function getFile();           // source filename
    final public  function getLine();           // source line
    final public  function getTrace();          // an array of the backtrace()
    final public  function getPrevious();       // previous exception
    final public  function getTraceAsString();  // formatted string of trace
You might ask why use getTrace() instead of getFile() and getLine()? Well because that will only get you the file and the line of the CURRENT file where the script is executing, what we want to get is where the call originated from, not this class, but the class which called the function in this class.

So what we can do when we create out own extended Exception class (MYException), is make use of this stack trace feature to resolve the true location of the error.

Code: Select all

<?php
class MYException extends Exception{

	private $debug;
	public function __Construct($message, $code = 0, $debug = false) {

		// construct the parent Exception class
		$this->debug = $debug;
		parent::__Construct($message, $code);

	}

	// custom string representation of object
	public function __toString() {
		if(!$this->debug){
			return "\r\n<strong id='error'>[{$this->code}]: {$this->message}</strong><br/>\n";
		}
		$_debug = $this->getTrace();
		$_str = $_debug[1]['class'].":".$_debug[1]['line'];
		return "\r\n<strong id='error'>{$_str} [{$this->code}]: {$this->message}</strong><br/>\n";
	}

	public function customFunction() {
		print "A custom function for this type of exception\n";
	}
}
?>
You will notice that in the __toString() function i have created my own little error string but the important part here is how i have used the getTrace() function.
Because getTrace() will always return entry [0] as the current class, we can specify the position [1] instead which gets us the class immediately next in line for the stack trace.
Note: This will ONLY resolve the next class in the chain where it is being called from. In most cases this would be true, but remember this is NOT a full stack trace, we just dont want to report the dbConnector class as the problem.

So now to use this trace, we can modify our query function to look like this instead.

Code: Select all

    public function query($query, $_return = false){
	
	try{

	    if($_return){

		return mysql_query($query, $this->link);

	    }

	    $this->result = mysql_query($query, $this->link);
	    
	    if(!$this->result){
		
		throw new MYException( sprintf("MySQL.Error: %s", mysql_error()), mysql_errno(), true); //notice the true here to enable the debugging?
		
	    }
	    
	    return true;
	    
	}catch (MYException $e){

	    /*
	     * We don't want to handle the error here, hand it back
	     * to the class calling the query.
	     */
	    throw $e;
	    
	}
	
    }
So now when we call the query function, we can directly handle the error inside of the class where the error is taking place and so that we dont manually handle errors with our database connector. This is achieved by simply throwing the existing MYException $e back to the class it came from.

And now, nothing changes in our original function (except that we should be using a try{}catch{} statement :wink:

Code: Select all

public function my_function(){
    try{

        $q = $this->query("select * from mytable where id = '1'");

        while($r = $this->fetch("assoc")){

            $row[] = $r;

        }

        return $row;

    }catch( MYException $e ){

        die($e);

    }
There is no need to check whether or not that $q was true or not, because if there was an error, we have already handled it through our MYException. You don't even need to throw an exception again here because we told the query() function that we were to send the exception back to the original class which gets caught in the my_function() catch statement 8) .

:drunk: