note: you should have a basic understanding of Object Oriented Programming
when it comes to debugging, I’m always glad that PHP throws very descriptive errors, sometimes even providing solutions. While using procedural PHP code, you normally have no problem locating the part of your code which is the troublemaker. Alas, the picture becomes different, if you start coding PHP OOP style. You have objects calling each other, even loading themselves (thanks to the georgeous PHP SPL). Loosing the overview is just a matter of time.
Exceptions can be considered as advanced Errors, they don’t just kill the script, but can be thrown and caught (i.e. you can repair the error) just as the program flows. A big advantage of Exceptions is, that they build a Stack (a list of every function in the current function stack, that the Exceptions travels through).
Example of Exception usage:
try
{
// try sending an email message via SMTP
// the EMail class will throw an Exception, if there is for instance no connection
$error_report = new EMail($emsg);
$error_report->send();
}
catch (Exception $e)
{
// if sending fails, at least log the error
error_log($emsg);
}
Example of an Exception StackTrace:
Quote
#0 /****/main.errorhandler.php(54): ErrorLog::add(Array, '')
#1 [internal function]: KBL_main_ErrorHandler(2, 'PDO::__construc...', '***', 50, Array)
#2 /****/class.adb.php(50): PDO->__construct('mysql:host=loca...', '***', '***')
#3 /****/class.adb.php(83): aDB::connect()
#4 /****/class.articledb.php(70): aDB::prepare('ids', '????SELECT?????...')
#5 /****/class.articledb.php(93): ArticleDB->makeStatement('ids', '????SELECT?????...')
#6 /****/class.articledb.php(177): ArticleDB->load()
#7 /****/class.articlehtml.php(106): ArticleDB->requestHTML()
#8 /****/presse.php(10): ArticleHTML->display()
#9 /****/class.htmlplus.php(270): include('***')
#10 /****/main.php(52): HTMLplus->Inhalt()
#11 {main}
#1 [internal function]: KBL_main_ErrorHandler(2, 'PDO::__construc...', '***', 50, Array)
#2 /****/class.adb.php(50): PDO->__construct('mysql:host=loca...', '***', '***')
#3 /****/class.adb.php(83): aDB::connect()
#4 /****/class.articledb.php(70): aDB::prepare('ids', '????SELECT?????...')
#5 /****/class.articledb.php(93): ArticleDB->makeStatement('ids', '????SELECT?????...')
#6 /****/class.articledb.php(177): ArticleDB->load()
#7 /****/class.articlehtml.php(106): ArticleDB->requestHTML()
#8 /****/presse.php(10): ArticleHTML->display()
#9 /****/class.htmlplus.php(270): include('***')
#10 /****/main.php(52): HTMLplus->Inhalt()
#11 {main}
what you can see in this code is which functions/methods have been called until the Error/Exception was caught. Sometimes it’s also important to know, which route the program was taking (since faulty parameters may be passed down for several levels before the cause an error).
but now back to topic. Exceptions are good and nice, but useless if you fail to extract the necessary information. In contrast to Errors, Exception do not cause a printed line on your page, so YOU have to take care of that.
The good news are: Exceptions are objects, now you can use your OOP skills to make them do what you want. Something you should do first is storing the Exceptions somewhere. Since Exceptions are caught everywhere, that has to be done in a globally accessible way. One possibility doing that uses the "Registry Pattern" (imagine a combination of the Singleton Pattern and an Array):
<?php
########################
# error storage object #
########################
/**
* the information available from the thrown exceptions are stored here
* for later processing (on destruct of main object)
*/
class ErrorEntry
{
/**
* @var (string) $time time the Exception is saved (= time thrown)
* @var (string) $method method that catches the exception
* @var (int) $line line that throws Exception
* @var (int) $code description code
* @var (string) $level severity level
* @var (string) $message Exception message
* @var (string) $file file that throws Exception
* @var (string) $trace trace of the Exception
* @var (array) $mp_levels transcription of the severity levels
* @var (array) $mp_labels translateable labels for some of the
* properties while printing
*/
protected $time;
protected $method;
protected $line;
protected $code;
protected $level;
protected $message;
protected $file;
protected $trace;
// if you want a translation
public static $mp_levels = array('Error', 'Warning', 'Notice');
public static $mp_labels = array('Function', 'File', 'Line');
/**
* constructor method setting all properties. time is computed since
* time differences shouldn't be much.
*
* @param (Exception) $exc the caught Exception
* @param (string) $catch method where Exception is caught (__METHOD__)
* @param (int) $level severity of the Exception, is overridden
* by the Exception's severity value
* @return (void)
*/
function __construct($exc, $catch, $level = 0)
{
# Exception time
$this->time = date('r');
# Exception caught in
$this->method = $catch;
// from ErrorException
if ($exc instanceof ErrorException)
{
$level = $exc->getSeverity();
}
$this->level = self::$mp_levels[$level];
// from Exception
$this->message = $exc->getMessage();
$this->code = $exc->getCode();
$this->line = $exc->getLine();
$this->file = $exc->getFile();
$this->trace = $exc->getTraceAsString();
}
/**
* read-only access to the properties.
*/
function __get($key)
{
$value = (isset($this->$key)) ? $this->$key : NULL;
return $value;
}
/**
* print Exception trace as string representation.
*/
function __toString()
{
return $this->trace;
}
/**
* HTML formatted output of the entry.
*
* @param (bool) $f_trace additionally printing backtrace?
* @return (string) HTML version of Entry
*/
public function writeHTML($f_trace = true)
{
$html = '
<h3>' . $this->level . ' [' . $this->code . ']</h3>
<p class="i temp">' . $this->time . '</p>
<p>' . $this->message . '</p>
<p>
' . self::$mp_labels[0] . ' : ' . $this->method . '<br />
' . self::$mp_labels[1] . ' : ' . $this->file . '<br />
' . self::$mp_labels[2] . ' : ' . $this->line . '
</p>';
if ($f_trace)
{
$html .= "\n<p>Backtrace:<br />" . $this->trace . '</p>';
}
return $html;
}
/**
* text output of the entry used for email (...)
*
* @return (string) text version of Entry
*/
public function writeText()
{
$string = "\n" . $this->level . ' [' . $this->code . '] - ' . $this->time . '
' . $this->message . '
' . self::$mp_labels[0] . "\t : " . $this->method . '
' . self::$mp_labels[1] . "\t : " . $this->file . '
' . self::$mp_labels[2] . "\t : " . $this->line . '
' . $this->trace . "\n";
return $string;
}
}
<?php
### ATTENTION, this class uses custom code in the submit() method,
### which is not included here (but can be substituted)
abstract class ErrorLog
{
/**
* @var (array) $Log collection of ErrorEntry objects
*/
private static $Log = array();
/**
* get the data collected in MyException() and create a new
* ErrorEntry object
*
* @param (array) $bundle an array as returned by the
* MyException::getBundle() method
* @param (string) $class class name where the Exception
* was caught
* @return (void)
*/
public static function add(
$input,
$catch,
$level = 0
)
{
if ($input instanceof Exception)
{
self::$Log[] = new ErrorEntry($input, $catch, $level);
}
// at this point there is some more fetching code,
// which depends on a custom Exception class
else
{
$emsg = "Incorrect parameter provided";
throw new Exception($emsg);
}
return true;
}
/**
* standard get function of Abstract Registry Pattern
*
* @param (string) $key index name of log array
* @return (mixed) NULL if key unknown
* ErrorEntry object
*/
public static function get(
$key
)
{
if (isset(self::$Log[$key]))
{
return self::$Log[$key];
}
return NULL;
}
/**
* get the complete log (part of the Abstract Registry Pattern)
*
* @return (array) log array of ErrorEntry objects
*/
public static function getAll()
{
return self::$Log;
}
/**
* part of the Abstract Registry Pattern
*
* @return (void)
*/
public static function reset()
{
self::$Log = array();
return true;
}
public static function first()
{
if (0 == count(self::$Log))
{
return NULL;
}
reset(self::$Log);
return current(self::$Log);
}
public static function next()
{
if (0 == count(self::$Log))
{
return NULL;
}
return next(self::$Log);
}
public static function prev()
{
if (0 == count(self::$Log))
{
return NULL;
}
return prev(self::$Log);
}
public static function last()
{
if (0 == count(self::$Log))
{
return NULL;
}
return end(self::$Log);
}
/**
* sending Error Report via Swift Mail implementation. if an error
* report is wanted in all applications add this method to the current
* objects destructor.
*
* @return (bool) submit success
*/
public static function submit()
{
if (empty(self::$Log)) return NULL;
$text = 'Server "' . $_SERVER['HTTP_HOST'] . '":
';
foreach (self::$Log as $entry)
{
$text .= $entry->writeText();
}
try
{
// this is a custom email implementation, replace with your own
$error_report = new EMail($text);
$error_report->send();
}
catch (Exception $e)
{
echo '<p>', $e->getMessage(), '</p>';
error_log($text);
return false;
}
return true;
}
}
now that we have the code for handling our Exceptions, we only need to call it properly.
to ease the loading, I recommend the SPL’s spl_autoload_register() function. what’s left is catching the Exception:
// connect to MySQL using PDO
try
{
$dsn = 'mysql:host=' . DB_SERVER . ';dbname=' . DB_NAME;
self::$PDO = new PDO($dsn, DB_USER, DB_PASS);
}
catch (PDOException $pdo)
{
# log the Exception, anything else will be done by the system
ErrorLog::add($pdo, __METHOD__);
// do something about that
}
and finally send me the details
// part of the top level class
class Main
{
// some hard working code
public function __destruct()
{
ErrorLog::submit();
}
}
what’s the benefit of that? Your users are not prompted with error messages (which btw. can be used to exploit your page), but you will be notified.
here is an example of the email body the above code produced
Quote
Server "localhost":
Error [12] - Mon, 25 Jan 2010 11:46:53 +0100
The ID "pod" is invalid.
Function : ID
File : /****/class.struktur.php
Line : 121
#0 /****/class.id.php(123): Struktur->checkID()
#1 /****/class.id.php(101): ID->validateID()
#2 /****/class.id.php(45): ID->buildSeite()
#3 /****/class.htmlplus.php(59): ID->__construct('main')
#4 /****/main.php(15): HTMLplus->__construct()
#5 {main}
Error [12] - Mon, 25 Jan 2010 11:46:53 +0100
The ID "pod" is invalid.
Function : ID
File : /****/class.struktur.php
Line : 121
#0 /****/class.id.php(123): Struktur->checkID()
#1 /****/class.id.php(101): ID->validateID()
#2 /****/class.id.php(45): ID->buildSeite()
#3 /****/class.htmlplus.php(59): ID->__construct('main')
#4 /****/main.php(15): HTMLplus->__construct()
#5 {main}
I hope this small article will give you some new impulses when you code your next project.
Dormi






MultiQuote





|