copyright (c)  2003   useGroup
PHP

Jochen Stärk
PHP-Tutorial
Debugging

nach untenVorgehensweise
nach untenprint_r
nach untentrigger_error
nach untenset_error_handler

Vorgehensweise

nach obenVorgehensweise 

Neben dem Einschalten der Notices sollte man sich Gedanken machen, ob man sich einen anständigen Debugger beschafft.

Kostenlos gibt es zum Beispiel für Windows ältere Versionen des Maguma Studio. Das ist zwar eine IDE, kein Debugger, aber dummerweise kann z.B. der DBG-Debugger nur durch eine IDE bedient werden, die ihn unterstützt. Sie können Breakpoints setzen, Variablen "watchen", das Programm schrittweise ausführen und damit Fehler sehr schnell erkennen.

Apropos schrittweise ausführen und Fehler erkennen: Mit einem guten eigenen Error-Handler mit debug_backtrace (ab PHP 4.3), sehen Sie nicht nur die Zeile, in der das Problem aufgetreten ist, sondern auch die Zeilen, die die entsprechenden Funktionen aufgerufen haben.

Profiler werden zwar von PHP unterstützt (register_tick_function), dem Autor ist dennoch derzeit kein guter bekannt. (Ein Profiler würde Ihnen sagen, welche Teile des Programms besonders Rechenzeit- oder Speicherintensiv sind). DBG ist zwar auch ein Profiler nur müssten die Informationen aus ausgewertet werden...

Weitere, hoffentlich nützliche Funktionen, die beim Debuggen helfen, sind hier aufgeführt.


print_r

nach obenprint_r  print_r($variable); oder $res=print_r($variable, true)

"print_r gibt Variablen [und damit auch Arrays und Objekte] in einer menschenlesbaren Form aus" und ist damit ziemlich praktisch, wenn man zum Beispiel gerade ohne Debugger debuggt. Leider liefert print_r kein HTML, sondern Plain-Text.

<?
$myObject=new myClass();
?>
<pre>
<?
print_r($myObject);
?>
</pre>

<!-- Ausgabe:
myclass Object
(
    [content] => Array
        (
            [aphorismen] => Array
                (
                    [Michael Richter 1952] => Array
                        (
                            [0] => Fanatismus ist Mord am Zweifel.
                            [1] =>  Kann man Frieden kriegen?
                            [2] => Laß mich bitte zu Ende dazwischen
           reden!
                        )

                )

            [sprueche] => Array
                (
                    [Konfuzius] => Array
                        (
                            [0] => Ein Mensch mag noch so herausragende F?igkeiten
           haben-wenn er arrogant und selbstsüchtig ist, sind sie nichts wert
                        )

                )

        )

)
-->
$myObject=new myClass();
print_r("Bitte auszugebende Variable nicht mit einem String vermischen: $myObject");
// Ausgabe: Bitte auszugebende Variable nicht mit einem String vermischen: Object
class myClass
{
  var $content;
  function myClass()
  {
    $this->content=array(
    "aphorismen"=>
      array("Michael Richter 1952"=>array("Fanatismus ist Mord am Zweifel.",
          " Kann man Frieden kriegen?", "Laß mich bitte zu Ende dazwischen
	   reden!")),
    "sprueche"=>
      array("Konfuzius"=>array("Ein Mensch mag noch so herausragende Fähigkeiten
           haben-wenn er arrogant und selbstsüchtig ist, sind sie nichts wert")));
  }

  function replaceNewLinesSpacesForHTML($input)
  {
    $res=$input;
    $res=str_replace("\n", "<br>",$res);
    $res=str_replace(" ", "&nbsp;",$res);
    return $res;
  }
  function write()
  {
    echo $this->replaceNewLinesSpacesForHTML(print_r($this->content, true));
  }
}

$myObject=new myClass();
echo "print_r in der Klasse mit bool return=true ";
$myObject->write();


trigger_error

nach obentrigger_error  trigger_error($fehlertext[, $typ]);

Es kann an verschiedenen Stellen sinnvoll sein, Fehlermeldungen zu produzieren. Wenn Sie eine Bibliothek schreiben, die Sie oder andere später noch nutzen wollen, ist es oft sinnvoll, statt mit DIE abzubrechen, eine Fehlermeldung per trigger_error zu produzieren.

Das hat zwei Nebenwirkungen, zum Einen, dass das Verhalten einer normalen Fehlermeldung angenommen wird, die zum Beispiel nicht auf dem Bildschirm ausgegeben, sondern direkt per Mail an einen Entwickler verschickt wird (das kann zum Beispiel relativ leicht durch http://www.php.net/manual/de/function.set-error-handler.php bewerkstelligt werden).

Zum Anderen kann es sinnvoll sein, eine solche Fehlermeldung zum Debuggen zu verwenden. trigger_error kann nicht nur "fatale Fehler" produzieren, die das Programm sofort zum Stillstand bringen, sondern auch einfache Warnungen ausgeben.

In beiden Fällen wird der Fehlertext mit der Zeilennummer und der Datei, in der er aufgetreten ist, ausgegeben.

Es macht theoretisch Sinn, die Debug-Anwendungsweise von trigger_error in einer speziellen Funktion zu kapseln. Tut man das nicht, kann man zwar beim Übergang in die Produktionsumgebung anhand eventuell noch auftretender Meldungen sehr schnell die entsprechenden Zeilen erkennen und säubern, jeden Pfad abzulaufen, um alle Meldungen mindestens einmal auftreten zu lassen, um sie zu grillen, ist jedoch nicht besonders zuverlässig, sprich, Sie könnten Meldungen übersehen. Das Problem dabei ist, dass mit DEFINEs keine Funktionen definiert werden können und mit create_function erzeugte Funktionen von PHP nicht als "inline" behandelt werden. Sie bekommen es also nicht hin, die Zeile, in der der Funktionsaufruf stattfand, zurück gegeben zu bekommen, Sie bekommen die Zeile in der selbstdefinierten Funktion.

Ein Kapseln der Bibliotheks-Weise würde dasselbe Problem bei Bibliotheksfunktionen beschwören. Eine Konstante, z.B. E_DEBUG, mit E_USER_ERROR zu definieren, beim Debuggen zu verwenden, und beim Ausliefern auf E_USER_NOTICE zu ändern, ist zwar relativ elegant, und tatsächlich werden normalerweise E_USER_NOTICEs nicht ausgegeben, jedoch sind unsere Produktionsserver so konfiguriert, dass Notices als Warnings zählen. Und es gibt keinen Fehlermeldungstyp, der komplett ignoriert würde. Ein PHP > 4.3 und debug_backtrace() bleiben dem Autor also nicht erspart.

Gültige Werte für "typ" sind:

E_USER_ERROR
E_USER_WARNING
E_USER_NOTICE
<?

error_reporting(E_ALL);

DEFINE("DEBUG_SEVERITY",E_USER_WARNING);
// Beim Ausliefern umstellen auf E_USER_NOTICE!

function mydeb($value)
{
   if ($value===0)
   {
     echo "ERROR:";
     var_dump(debug_backtrace());  // Diese Zeile funktioniert nur mit PHP 4.3 und höher
     trigger_error("Bibliotheksfehler. Bitte myfunc nicht mit 0 aufrufen.", E_USER_ERROR);
   }
}

trigger_error("Hier wird die Funktion korrekt aufgerufen.", DEBUG_SEVERITY);
mydeb("hallo welt");
trigger_error("... und hier falsch.", DEBUG_SEVERITY);
mydeb(0);

/*
Ausgabe:

Warning:  Hier wird die Funktion korrekt aufgerufen. in /srv/www/htdocs/test/testphp.php on line 18

Warning:  ... und hier falsch. in /srv/www/htdocs/test/testphp.php on line 20
ERROR:array(1) {
  [0]=>
  array(4) {
    ["file"]=>
    string(32) "/srv/www/htdocs/test/testphp.php"
    ["line"]=>
    int(21)
    ["function"]=>
    string(5) "mydeb"
    ["args"]=>
    array(1) {
      [0]=>
      &int(0)
    }
  }
}

Fatal error:  Bibliotheksfehler. Bitte myfunc nicht mit 0 aufrufen. in /srv/www/htdocs/test/testphp.php on line 14


*/

?>


set_error_handler

nach obenset_error_handler  set_error_handler($neuerHandler);

Ein eigener Error_Handler kann (ab PHP 4.2) auch durchaus mehr als nur die Zeile zeigen, in der der Fehler aufgeteten ist. Dieser zeigt zum Beispiel auch die Zeile, wo der Fehler hergekommen ist, wenn beispielsweise eine Funktion mit falschen Parametern aufgerufen wurde: Denn dann zeigt er nicht nur die Zeile in der Funktion, sondern auch die Zeile des Aufrufs.

function print_my_backtrace($severity,$arr)
{
$bgColor="808070";
if (($severity==E_COMPILE_ERROR)||($severity==E_CORE_ERROR)||($severity==E_USER_ERROR)||($severity==E_ERROR)) $bgColor="A08070";

echo "<table style='background-color:#$bgColor;color:#ffffff;font-size:8pt;'>";
foreach ($arr as $linebefore)
{
    if (isset($linebefore["file"])) echo "<tr><td>file</td><td>".$linebefore["file"]."</td></tr>";
    echo "<tr><td>function</td><td>".$linebefore["function"]."</td></tr>";
    if (isset($linebefore["line"])) echo "<tr><td>line</td><td>".$linebefore["line"]."</td></tr>";
    echo "<tr><td colspan=2><hr></td></tr>";
}
echo "</table>";
}

function myErrorHandler($errno, $errstr, $errfile, $errline) {
switch ($errno) {
case E_COMPILE_ERROR:
case E_CORE_ERROR:
case E_USER_ERROR:
case E_ERROR:
 echo "<div style='width:50%; border-style: solid; border-width:1px; border-color: red; margin-top:10px;'>\n";
 echo "<br><b>FATAL</b> [$errno] $errstr<br />\n";

 print_my_backtrace($errno,debug_backtrace());

 echo "  Fatal error in line $errline of file $errfile";
 echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
 echo "Aborting...<br />\n";
 echo "</div>\n";
 exit(1);
 break;

case E_PARSE:
case E_COMPILE_WARNING:
case E_CORE_WARNING:
case E_USER_WARNING:
case E_WARNING:
 echo "<div style='width:50%; border-style: solid; border-width:1px; border-color: red; margin-top:10px;'>\n";
 echo "<br><b>ERROR</b> [$errno] $errstr<br />\n";
 print_my_backtrace($errno,debug_backtrace());
 echo "</div>\n";
 break;
case E_USER_NOTICE:
case E_NOTICE:
 echo "<div>\n";
 echo "<br><b>WARNING</b> [$errno] $errstr<br />\n";
 print_my_backtrace($errno,debug_backtrace());
 echo "</div>\n";
 break;
default:
 echo "<div>\n";
 echo "Unkown error type: [$errno] $errstr<br />\n";
 echo "</div>\n";

 break;
}
}

set_error_handler("myErrorHandler");

Kleiner Wermutstropfen: Compiler-Fehler, wie etwa ein falschgeschriebener Funktionsname, die dazu führen, dass PHP das Script nichtmal starten kann, können auch so nicht abgefangen werden.

Devshed hat unter http://www.devshed.com/c/a/PHP/Error-Handling-In-PHP-part-1/5/ übrigens eine Anleitung, wie man auftretende PHP-Fehler in eine MySQL-Tabelle einträgt.


copyright (c)  2003   useGroup