Mein eigenes MVC-Framework: Der Debugger und die Log-Datei

<< Zurück zur Übersicht

Der Debugger ist während der Entwicklung eines Projekts eines der wichtigsten Hilfsmittel. Man möchte gerne sofort sehen, wo der Fehler liegt, um ihn möglichst schnell beheben zu können.
Ist die Entwicklung abgeschlossen, will man aber, dass keine Fehlermeldungen mehr ausgegeben werden. Wenn man aber trotzdem noch über Fehler informiert werden will, gibt es noch die Möglichkeit einer Log-Datei.

Der Sinn der Debugger-Klasse (FW_Debugger) ist es also, diese Funktionen zu vereinen.
Die Klasse holt sich die Einstellungen aus der Configurationsklasse (FW_Config).
Außerdem ist FW_Debugger ein Singleton, da wir ja nicht mehrere Debugger benötigen.

Es gibt folgende Methoden:

  • getInstance (Singleton)
  • __construct (nimmt die Einstellungen mithilfe von FW_Config vor)
  • Error($message, $file, $line, $exception)
  • dieError($message, $file, $line, $exception)
  • logFile($message, $file, $line)
  • logScreen($message, $file, $line, $die)
  • FW_Debugger::registerErrorHandler()
  • FW_Debugger::handleError($errno, $errstr, $errfile, $errline, $context)

Und folgende Eigenschaften:

  • object $instance (Speichert das Objekt in der statischen Variable $instance)
  • array $properties (Speichert die Einstellungen, die aus der Config gelesen werden)

Der Entwurf des Konzepts für den Debugger war ziemlich kompliziert. Das Konzept ist auch noch nicht 100%ig ausgereift. Aus Erfahrung weiß ich, dass die besten Ideen sowieso erst kommen, wenn es schön zu spät ist 😛

Den PHP-Errorhandler überschreiben

Mit der PHP-Funktion set_error_handler kann man den Standart-Errorhandler von PHP überschreiben und einen eigenen dafür einsetzen. In meinem Framwork heißt der Errorhandler FW_Debugger::handleError() und wird so festgelegt:

/**
   * @access public
   * Registriert die statische methode handleError als error_handler
   */
  public static function registerErrorHandler()
  {
    set_error_handler(array("FW_Debugger", "handleError"));
  }

Ist das Argument von set_error_handler ein Array, repräsentiert der erste Wert den Namen der Klasse und der zweite den namen der STATISCHEN Methode dieser Klasse, die den Errorhandler bereitstellt.

Wenn jetzt ein Fehler bzw. eine Warnung von PHP kommt, wird sie an handleError() übergeben.

FW_Debugger::handleError():

/**
   * @access public
   * @param int errno
   * @param string errstr
   * @param string errfile
   * @param int errline
   * @param mixed context
   * Behandelt Fehler, die über den error_handler kommen
   */
  public static function handleError($errno, $errstr = '', $errfile = '', $errline = '', $context = null)
  {
    $debugger = FW_Debugger::getInstance();
    $config   = FW_Config::getInstance();
    if($errno = E_NOTICE || $errno = E_STRICT)
    {
      if($config->get("debugger_handle_unimportant") == "1")
      {
        $debugger->Error($errno.": ".$errstr, $errfile, $errline, false);
      }
    }
    else
    {
      $debugger->Error($errno.": ".$errstr, $errfile, $errline, false);
    }
  }

$errno enthält die Fehlernummer, z.B. steht 8 für eine E_NOTICE.
$errstr enthält die Fehlernachricht
$errfile enthält die Datei, die den Fehler verursacht hat
$errline enhält die Zeile, in der der Fehler auftrat
$context ist ein Array mit Zusatzinformationen.

Diese Methode ist statisch, weil PHP nicht mit einer Instanz von FW_Debugger arbeitet, sondern mit der Klasse selbst.

Mit FW_Debugger::registerErrorHandler(); kann der ErrorHandler registriert werden. Am besten macht man das in der index.php vom Projekt.
Als erstes werden die Objekte $debugger und $config angelegt und dann wird überprüft, ob es sich beim Fehler um eine E_NOTICE oder E_STRICT handelt.
Falls das der Fall ist und debugger_handle_unimportant auf 0 steht, wird der Fehler ignoriert. Falls debugger_handle_unimportant auf 1 steht, wird der Fehler mithilfe von $debugger geloggt.

Fehler, die nicht vom Typ E_STRICT oder E_NOTICE sind, werden in jedem Fall an $debugger übergeben.

Soviel zum Errorhandler, jetzt kommen wir zum eigentlichen Debugger.

Die 2 Fehlerarten: Error und dieError

Der Unterschied zwischen dieError und Error ist, dass bei dieError die Ausführung des Skripts abgebrochen wird und bei Error nicht.

Hier die 2 Methoden:

/**
   * @access public
   * @param string message
   * @param string file
   * @param string line
   * @param bool exception
   * Diese Methode gibt die Fehler an die entsprechenden Methoden weiter und wirft
   * bei Bedarf eine Exception
   */
  public function Error($message, $file = null, $line = null, $exception = false)
  {
    if($this->properties["file_logging"] == 1)
    {
      $this->logFile($message, $file, $line);
    }

    if($this->properties["screen_logging"] == 1)
    {
       $this->logScreen($message, $file, $line, false);
    }

    if($exception === true)
    {
      throw new FW_Exception($message);
    }
  }

  /**
   * @access public
   * @param string message
   * @param string file
   * @param string line
   * @param bool exception
   * Diese Methode gibt die Fehler an die entsprechenden Methoden weiter und wirft
   * bei Bedarf eine Exception.
   * Bricht das Script ab.
   */
  public function dieError($message, $file = null, $line = null, $exception = false)
  {
    $this->Error($message, $file, $line, $exception);
    exit();
  }

dieError() ruft eigentlich nur Error() auf und beendet danach das Skript mit exit().
Error() prüft, ob Ausgabe, Log-Datei oder Exceptions aktiviert sind und arbeitet die 3 Dinge nacheinander ab.

Die 2 Logging-Arten: logScreen und logFile
logScreen zeigt wie der Name schon sagt die Meldungen auf dem Bildschrim an und
logFile schreibt die Meldungen in eine Log-Datei.

/**
   * @access protected
   * @param string message
   * @param string file
   * @param string line
   * Speichert einen neuen Eintrag in das Logfile.
   * (mit IP, Zeit, Zeile und Datei)
   */
  protected function logFile($message, $file, $line)
  {
    $handle = fopen($this->properties["file"],'a');
    if($handle)
    {
      $string = date("d.m.Y - H:i:s", time())." | ".$_SERVER["REMOTE_ADDR"]." | ";
      $string .= "File: ".$file;
      $string .= " | Line: ".$line;
      $string .= " | ".$message;
      $string .= "rn";
		  fwrite($handle,$string);
		  fclose($handle);
		}
		else
		{
      throw new Exception("Logfile-Error");
    }
  }

  /**
   * @access protected
   * @param string message
   * @param string file
   * @param string line
   * @param bool die
   * Gibt eine Meldung aus und bricht je nach $die das Skript ab
   */
  protected function logScreen($message, $file, $line, $die)
  {
    $message = "Error: ".$message." rn";
    $message .= ($file == null) ? "" : "File: ".$file."rn";
    $message .= ($line == null) ? "" : "Line: ".$line."rn"; 

    if($die == true)
    {
      die($message);
    }
    else
    {
      echo $message;
    }
  }

Es wird die Zeilennummer, die IP, Die Zeit, der Datename und die Fehlermeldung abgespeichert. Ausgegeben werden nur die Zeile, die Datei und die Errormessage.

Warum muss $file und $line übergeben werden?
Man könnte sich fragen, warum nicht einfach __FILE__ und __LINE__ genutzt werden. Ganz einfach!
__FILE__ würde immer die Zeile enthalten, an der __FILE__ in der Klasse steht. Das würde wenig bringen und deshalb wird __FILE__ an die Methode übergeben und ist dann in $file verfügbar.
Mit $line sieht es genau gleich aus.

Code
Hier ist die komplette Klasse FW_Debugger:

/**
 * SINGLETON
 * Debugger-Klasse für das Framework
 * @author Simon
 * @version 1
 * @package MVC-Framework
 *
 * Diese Klasse ist dazu da, das Projekt zu debuggen.
 * Man kann einstellen, ob die Meldungen ausgegeben werden sollen (für die Entwicklung sinnvoll)
 * und ob die daten geloggt werden sollen (für produktivbetrieb sinnvoll)
 *
 * Die Klasse bietet eine statische Methode zum registrieren des Errorhandlers von PHP.
 */
class FW_Debugger
{
  /**
   * @access protected
   * @var object instance
   * Speichert die Instanz der Klasse
   */
  protected static $instance = null;

  /**
   * @access protected
   * @var array properties
   * Speichert die Klasseneinstellungen
   */
  protected $properties = array();

  /**
   * Für Singleton
   */
  private function __clone() {}

  /**
   * @return object
   * @access public
   * getInstance gibt eine Instanz zurück und sorgt dafür, dass es nur eine gibt.
   * (Singleton)
   */
  public static function getInstance()
  {
    if(self::$instance === null)
    {
      self::$instance = new FW_Debugger();
    }
    return self::$instance;
  }

  /**
   * @access protected
   * Der Konstruktor initialisiert die Einstellungen
   */
  protected function __construct()
  {
    $config = FW_Config::getInstance();
    $this->properties["screen_logging"] =     $config->get("debugger_screen_logging");
    $this->properties["file_logging"] =       $config->get("debugger_file_logging");
    $this->properties["file"] =               $config->get("debugger_log_file");
  }

  /**
   * @access public
   * @param string message
   * @param string file
   * @param string line
   * @param bool exception
   * Diese Methode gibt die Fehler an die entsprechenden Methoden weiter und wirft
   * bei Bedarf eine Exception
   */
  public function Error($message, $file = null, $line = null, $exception = false)
  {
    if($this->properties["file_logging"] == 1)
    {
      $this->logFile($message, $file, $line);
    }

    if($this->properties["screen_logging"] == 1)
    {
       $this->logScreen($message, $file, $line, false);
    }

    if($exception === true)
    {
      throw new FW_Exception($message);
    }
  }

  /**
   * @access public
   * @param string message
   * @param string file
   * @param string line
   * @param bool exception
   * Diese Methode gibt die Fehler an die entsprechenden Methoden weiter und wirft
   * bei Bedarf eine Exception.
   * Bricht das Script ab.
   */
  public function dieError($message, $file = null, $line = null, $exception = false)
  {
    /*
    if($this->properties["file_logging"] == 1)
    {
      $this->logFile($message, $file, $line);
    }

    if($this->properties["screen_logging"] == 1)
    {
       $this->logScreen($message, $file, $line, true);
    }

    if($exception === true)
    {
      throw new FW_Exception($message);
    }*/

    $this->Error($message, $file, $line, $exception);
    exit();
  }

  /**
   * @access protected
   * @param string message
   * @param string file
   * @param string line
   * Speichert einen neuen Eintrag in das Logfile.
   * (mit IP, Zeit, Zeile und Datei)
   */
  protected function logFile($message, $file, $line)
  {
    $handle = fopen($this->properties["file"],'a');
    if($handle)
    {
      $string = date("d.m.Y - H:i:s", time())." | ".$_SERVER["REMOTE_ADDR"]." | ";
      $string .= "File: ".$file;
      $string .= " | Line: ".$line;
      $string .= " | ".$message;
      $string .= "rn";
		  fwrite($handle,$string);
		  fclose($handle);
		}
		else
		{
      throw new Exception("Logfile-Error");
    }
  }

  /**
   * @access protected
   * @param string message
   * @param string file
   * @param string line
   * @param bool die
   * Gibt eine Meldung aus und bricht je nach $die das Skript ab
   */
  protected function logScreen($message, $file, $line, $die)
  {
    $message = "Error: ".$message." rn";
    $message .= ($file == null) ? "" : "File: ".$file."rn";
    $message .= ($line == null) ? "" : "Line: ".$line."rn"; 

    if($die == true)
    {
      die($message);
    }
    else
    {
      echo $message;
    }
  }

  /**
   * @access public
   * Registriert die statische methode handleError als error_handler
   */
  public static function registerErrorHandler()
  {
    set_error_handler(array("FW_Debugger", "handleError"));
  }

  /**
   * @access public
   * @param int errno
   * @param string errstr
   * @param string errfile
   * @param int errline
   * @param mixed context
   * Behandelt Fehler, die über den error_handler kommen
   */
  public static function handleError($errno, $errstr = '', $errfile = '', $errline = '', $context = null)
  {
    $debugger = FW_Debugger::getInstance();
    $config   = FW_Config::getInstance();
    if($errno == E_NOTICE || $errno == E_STRICT)
    {
      if($config->get("debugger_handle_unimportant") == "1")
      {
        $debugger->Error($errno.": ".$errstr, $errfile, $errline, false);
      }
    }
    else
    {
      $debugger->Error($errno.": ".$errstr, $errfile, $errline, false);
    }
  }
}

Anwendung

Angewandt wird die Klasse so:

Als erstes stellt man in seiner config.ini ein, wie der Debugger arbeiten soll:

debugger_file_logging   = 1
debugger_screen_logging = 1
debugger_log_file    = ../logfile.txt
debugger_handle_unimportant = 1

Danach sollte man den Errorhandler mit FW_Debugger::registerErrorHandler(); registrieren lassen.

Jetzt kann man auch schon mit dem Debugger arbeiten!

z.B. so:

$debugger = FW_Debugger::getInstance();
$debugger->dieError("bricht das skript ab", __FILE__, __LINE__);
$debugger->Error("bricht das skript NICHT ab", __FILE__, __LINE__);
$debugger->Error("bricht das skript auch NICHT ab, wirft aber eine Exception", __FILE__, __LINE__, true);

Diese Klasse ist sicherlich nicht 100%ig perfekt, wird aber hier aktualisiert, falls ich sie bei mir im Framework verbessere.

Die View-Klassen

1 Star2 Stars3 Stars4 Stars5 Stars (1 Stimme, durchschnittlich 3,00 / 5)
Loading...


5 Kommentare zu “Mein eigenes MVC-Framework: Der Debugger und die Log-Datei”

  1. […] MySQL, HTML, JavaScript, AJAX, usw… « Blogparade: DSL-Anbieter-Vergleich Mein eigenes MVC-Framework: Der Debugger und die Log-Datei […]

  2. […] Der Debugger […]

  3. […] Mein eigenes MVC-Framework: Der Debugger und die Log-Datei (87) […]

  4. […] kommen wir zum Debugger Share and […]

  5. Die aktuellste Version meines HMVC-Frameworks erhaltet ihr ab sofort immer hier: http://www.net-developers.de/blog/2011/02/13/download-info-shfw-hmvc-framework-in-php/

Hinterlasse einen Kommentar!

Time limit is exhausted. Please reload the CAPTCHA.

»Informationen zum Artikel

Autor: Simon
Datum: 18.10.2008
Zeit: 03:21 Uhr
Kategorien: Mein MVC-Framework
Gelesen: 11826x heute: 2x

Kommentare: RSS 2.0.
Diesen Artikel kommentieren oder einen Trackback senden.

»Meta