4.11.2007

Fehlerbehandlung in PHP mittels Exception handling

Seit PHP 5.2 gibt es nicht nur die Möglichkeit, mittels try and catch nicht nur Code zu umschachteln, sondern auch wirklich Fehler zu fangen. Immer wieder findet man Codebeispiele, die die nicht-typisierte Eigenschaft von PHP gnadenlos ausnutzen und anstatt des erwarteten Datentyps einen Fehler aus einer Funktion zurück gibt.

In typisierten, höheren Programmiersprachen ist die natürlich nicht möglich. Das heißt aber nicht, dass man an jede Funktion einen referenziellen Parameter anfügt, mit dem man eventuelle Fehler auslesen kann. Das magische Stichwort heißt deshalb: Fehlerbehandlung mittels Exception-Behandlung.

Um Fehler ordnungsgemäß zu behandeln, kann man sich in PHP dem dem Fehlerabfang und -werfkonstruktes bedienen. Dazu sollte man sich zu erst Ableitungen der Klasse Exception erstellen:

  1. class FSException extends Exception
  2. {
  3. /**
  4.   * Vorher gefangene Exception
  5.   *
  6.   * @var Exception
  7.   */
  8. protected $objInnerException = null;
  9. protected $intType = self::HINWEIS;
  10.  
  11. const HINWEIS = 0;
  12. const FEHLER = 1;
  13. const SCHWERER_FEHLER = 2;
  14.  
  15. public function __construct($intCode, $strText,
  16. $intType = self::FEHLER, $objInnerException = null)
  17. {
  18. $this->objInnerException = $objInnerException;
  19. $this->intType = $intType
  20. parent::__construct($strText, $intCode);
  21. }
  22.  
  23. public function __toString()
  24. {
  25. return "Hier kann man alle gesammelten Exceptions ausgeben.";
  26. }
  27. }
  28.  
  29. final class FSDatenbankException extends FSException
  30. {
  31. private $intSqlErrorCode = 0;
  32. private $strSqlErrorText = "";
  33.  
  34. public function __construct($intSqlCode, $strSqlText, $intCode,
  35. $strText, $intType = self::FEHLER, $objInnerException = null)
  36. {
  37. $this->intSqlErrorCode = $intSqlCode;
  38. $this->strSqlErrorText = $strSqlText;
  39. parent::__construct($intCode, $strText, $intType,
  40. $objInnerException);
  41. }
  42.  
  43. public function getErrorText()
  44. {
  45. return $this->getMessage();
  46. }
  47. }
  48.  

Die eigenen Klassen haben zwei große Vorteile: Jede Instanz kann die vorher gefangene Exception halten, sodass sich ein Pfad vom Ursprung des Fehlers bis hin auf die höchste Ebene nachverfolgen läßt, der bei der Fehlersuche und -behebung einen ungemeinen Vorteil bringt.
Des weiteren kann man mit Hilfe des Fehlertypes sein weiteres Programm steuern. So kann man beim Fangen der Fehler explizit auf den Fehlertyp achten und zum Beispiel bei Hinweisen nur Texte ausgeben, bei Fehlern dem entsprechend anders reagieren.

Das folgende Beispiel soll die Fehlerbehandlung besser visualisieren:

  1. class TestDatenbank
  2. {
  3. /**
  4.   * Holt Daten und gibt diese in einem Array zurück
  5.   *
  6.   * Holt Daten aus der Datenbank und liefert ein Array und ...
  7.   *
  8.   * @param int $intId Text Id
  9.   * @return array
  10.   * @throws FSException
  11.   */
  12. public function holeDaten($intId)
  13. {
  14. try
  15. {
  16. if (is_numeric($intId))
  17. {
  18. try
  19. {
  20. if ($objResult = $this->holeDaten())
  21. {
  22. /* ... */
  23. }
  24. else throw new FSException(mysql_errno(), mysql_error(),
  25. 123, "Keine Daten");
  26. }
  27. catch (FSException $objException)
  28. {
  29. throw new FSException(10000, "Fehler beim Holen",
  30. FSException::FEHLER, $objException);
  31. }
  32. catch (Exception $objException)
  33. {
  34. throw new FSException(10000, "Schwerer Fehler beim Daten-
  35. holen", sFSException::SCHWERER_FEHLER, $objException);
  36. }
  37. }
  38. else throw new FSException(1000, "holeDaten-Aufruf doof!");
  39. }
  40. catch (FSException $objException)
  41. {
  42. throw new FSException(10000, "Fehler in TestDB->holeDaten",
  43. FSException::FEHLER, $objException);
  44. }
  45. catch (Exception $objException)
  46. {
  47. throw new FSException(10000, "Schwerer Fehler in TestDB->
  48. holeDaten", sFSException::SCHWERER_FEHLER, $objException);
  49. }
  50. }
  51. }
  52.  
  53. try
  54. {
  55. $objDatenbank = new TestDatenbank();
  56. $objDatenbank->holeDaten(1);
  57. }
  58. catch (FSException $objException)
  59. {
  60. /* Fehler ausgeben und loggen */
  61. }
  62. catch (Exception $objException)
  63. {
  64. /* Fehler ausgeben, loggen, Mail an Admin */
  65. }
  66.  

Hierbei ist es wichtig darauf zu achten, dass man die abgeleiteten Exceptions zuerst fängt, da eine abgeleitete Exception auch mittel catch (Exception $objException) gefangen werden können.

Mit diesem Konstrukten wird eine reibungslose Abwicklung des Codes auch bei Fehlern gewährt und es nie wieder Fehler geben, die bis zum Benutzer vordringen. Mit beliebigen Ableitungen und einer Exception-Trace-Visualisierungsklasse kann man im Debugmodus perfekt Fehlerbäume bauen, die einem bequemen Debuggen sehr nahe kommen.

C# und Java-Entwickler werden diese Dinge kennen...

In Kombination mit PHPDoc und sensitiver Codevervollständigung kann mein seinen PHP-Code näher an der Perfektion bringen:

PHP Funktionsdetails

 

Und mal ehrlich, wer will denn sowas?

  1. /**
  2.  * Ich soll einen Sting liefern, bin kann aber auch
  3.  * ein Boolean liefern. Dann aber mit Error array...
  4.  *
  5.  * @param int $intId Irgendeine ID
  6.  * @param array $arrError Error array
  7.  * @return string ODER false???
  8.  */
  9. function test($intId, &$arrError)
  10. {
  11. if ($intId)
  12. {
  13. return "string";
  14. }
  15. else
  16. {
  17. $arrError["code"] = 123;
  18. return false;
  19. }
  20. }
  21.  

Hinterlasse einen Kommentar

Dein Kommentar:

Kategorien