phpbar.de logo

Mailinglisten-Archive

[php] guter code? fehlermeldung mit pointer

[php] guter code? fehlermeldung mit pointer

Ulf Wendel UW_(at)_NetUSE.DE
Mon, 24 Jul 2000 18:39:35 +0200


daniel lorch wrote:
> 
> ist es "guter code" wenn ich die fehlermeldung per pointer übergebe? ich
> mache ein beispiel:
> 
> function hallo(&$err)
> {
>   if(!mach_was())
>   {
>     $err="Es ist fehlgeschlagen, weil ...";
>     return false;
>   }
> 
>   return true;
> }
> 
> if(hallo($err))
>   echo "Alles Oki";
> else
>   echo "FEHLER: $err";

Fehlerbehandlung in PHP ist ein heißes Thema: es geht einfach
nicht richtig sauber, die Sprache bietet nicht die notwendigen
Mittel. Die Behandlung von Fehlern in PHP kann den Ansätzen aus
verschiedenen Sprachen folgen. C und Java stellen zwei
wesentliche Varianten vor, die jeweils ihre spezifischen Vor- und
Nachteile haben. PHP scheint einen Zwischenweg zu nehmen.

Klassiche Fehlerbehandlungarbeitet mit zwei global ansprechbaren
Variablen $errno und $error. $errno enthält einen eindeutigen
Integerwert zur Identifizierung der Stelle im Programm, die den
Fehler meldet. $error enthält eine Klartext Fehlermeldung. $errno
und $error werden i.d.R. durch eine Funktion gesetzt, die von
fehlerverursachenden Code aufgerufen wird. Handelt es sich bei
dem Code der den Fehler verursacht um eine Funktion, wird die
Abarbeitung unterbrochen. Ob ein Wert und wenn ja welcher
zurückgeliefert wird ist unklar. Ebenso die Frage ob der Fehler
direkt ausgegeben wird.

Übertragt man dies in die Welt von PHP ergibt sich solcher oder
ähnlicher Code (PHP3, prozedual):

<?php

$errno = false;
$error = "";

function set_error($no, $msg) {
  global $errno, $error;
  $errno = $no;
  $error = $msg;
}

function cause_error() {

  set_error(1, "Just a silly test");
  return;
  
}

cause_error();
if ($errno)
 die($error);

?>

Dieser Code ist sehr einfach, bietet sehr viel Flexibilität und
genügt vollauf zur Fehlerbehandlung in kleinen Skripten. Doch das
Erkennen von Fehlern ist etwas mühsam. Es gibt globale Variablen,
die nicht als "protected" ("schreibgeschützt") deklariert werden
können und nur allzu leicht irgendwo einmal überschrieben werden
können. Schöner wäre ein derartiger Ansatz (PHP3, prozedual):

<?php
[...]

function cause_error() {

  set_error(1, "Just a silly test");
  return false;

}

if (!cause_error()) 
 handle_error();

?>

Nun ist dies zwar kürzer und auch etwas "sicherer" als die
Abfrage von $errno aber es wirft ein neues Problem auf. Die kann
man einen gültigen Returnvalue von einem ungültigen
unterscheiden? false könnte eine korrekte Antwort von
ist_Primzahl() sein. Dies gilt selbstverständlich auch für jeden
anderen Wert der vereinbart wurde um einen Fehler zu kennzeichen. 

Selbst wenn false in keiner Funktion ein gültiger Returnwert ist,
sind Schwierigkeiten vorprogrammiert. Die Funktion verspricht ein
Array zu liefern, welchselt bei einem Fehler jedoch den
Returntype. Wird der Fehler nicht abgefangen, drohen Folgefehler.
Statt des erwarteten Array wurde ein skalarer Wert geliefert,
Arrayzugriffe schlagen fehl.

Dieses Problem wurde schnell erkannt und ein neues Konstrukt
try-catch eingeführt. Potentiell fehlerverursachender Code kann
in Java (C++) in einem try Block gehalten werden und im
anschließenden catch wird eine etwaige Ausnahme (exception)
"gefangen". 

<?java_schematisch

 try {
     
   das macht Ärger;

 } catch ( Ausnahme Testfehler ) {
  
   Fehler behandeln;

 }  
   
?>

PHP kennt dieses Konstrukt nicht und wird es wohl auch nicht in
naher Zukunft kennenlernen. Ganz im Gegenteil die Entwicklungen
laufen gerade in zwei verschiedene Richtungen.

Eine Exception in Java ist ein von Throwable abgeleitetes Objekt,
welches die Fehlermeldung und den Typ (Typ des Objekt) kapselt
und einige Funktionen zur Abfrage des Fehlers bereithält. Die
Vorteile der Kapselung liegen auf der Hand, nur leider paßt
dieses Konzept kaum in die PHP Welt. Dennoch favorisiere ich
diesen Weg.

In PHP4 gibt es drei neue, bislang undokumentiere Funktionen, die
den klassischen Weg ausbauen:

- trigger_error( string $message [, int $error_type ] )
- set_error_handler( string $name_of_error_handler_function )
- restore_error_handler()
(plus einige Aliase)

trigger_error() erzeugt eine Fehlermeldung von einem bestimmten
Typ ( E_NOTICE, E_WARNING, E_ERROR ), die der Funktion abgefangen
werden kann, die mit set_error_handler() spezifiziert wurde.
restore_error_handler() ist das "Undo" für set_error_handler(). 

Diese Lösung hat einen gewissen Charme, weil eine zentrale
Funktion zur Fehlerbehandlung zur Verfügung steht, die den Fehler
drucken, loggen, vermailen oder sonstwie bearbeiten kann.
Innerhalb eines Skripts durchaus ein guter Weg. Was passiert
jedoch, wenn Code wiederverwendet werden soll, Bibliotheken
eingebunden werden und jede Bibliothek ihre eigenen Error Handler
definiert?

Derzeit schlage ich deshalb einen Zwischenweg vor der sich ein
klein wenig aus allen Töpfen bedient. Etwas Kapselung von Java
gepaart mit einem optionalen trigger_error(), halt_on_error und
auto_handle_error (PHP4, Mischung aus PEAR und Ulk). 

<?php
class error {

 var $message = "";
 var $file = "";
 var $line = -1;

 var $type = E_ERROR;

 var $trigger_error = false;
 var $halt_on_error = false;
 var $auto_handle_error = false;

 function error($message, $file = _FILE_, $line = _LINE_) {
   
   $this->message = $message;
   $this->file = $file;
   $this->line = $line;

   if ($trigger_error) 
     trigger_error($this->getLocalizedMessage(), $this->type);

   if ($auto_handle_error)
     $this->handle_error();

   if ($halt_on_error)
     die($this->getLocalizedMessage());
 
 }

 function getMessage() { [...] }
 function getLocalizedMessage() { [...] }
 function handleError() { ... }

}
?>

Die Klassenvariablen trigger_error, halt_on_error und
auto_handle_error sind per Default auf false gesetzt und sollten
nur während der Entwicklungsphase andere Werte annehmen. 

Von dieser Basisklasse werden die eigenen Fehlerklassen
abgeleitet. Will man einen Fehler "werfen" so greift man wieder
auf einen Zwischenweg (PHP4, OO):

<?php

class joedoes {
 
 var $err;

 function causeError() {
  $this->err = new ErrorItsMine("Just a silly test.", __FILE__,
__LINE__);
 } 

 function isError() {
  return ( if ( is_object($this->err) &&
(isset($this->err->type)) && ("Error"==substr($this->err->type))
) ) ? true : false;
 }
 
} 
?>
(Geht auch mit :: bei kleinen Änderungen) 

Leider ist das "catchen" jedoch wieder "aufwendig":

<?php

 $j = new joedoes; 
 $ok = $j->causeError();
 if (isError($ok))
   print $ok->getLocalizedMessage();
?>

Wie man sieht ist auch das nicht die ideale Lösung, dennoch wird
es wahrscheinlich der Weg sein, den PEAR nimmt. Etwas hier, etwas
da und alle betteln Zeev an try-catch() zu implementieren.

Was sollst Du nun machen? Die Referenzen bringen Dir überhaupt
nichts, im Gegenteil ich habe die Erfahrung gemacht das sie etwas
langsamer sind als ein passed-by-value (10%). Wenn Du auf PHP3
setzt nimm den ersten Ansatz den ich skizziert habe, bei PHP4
kannst Du auf trigger_error() setzen und bei großen Projekten
solltest Du mit dem PEAR Ansatz experimentieren.

Ulf

-- 
Ulf Wendel
NetUSE AG, Siemenswall, 24107 Kiel, Germany
Fon: +49 431 386435 00  --  Fax: +49 431 386435 99


php::bar PHP Wiki   -   Listenarchive