Hey hey,
wollte nur mal meine 'neue' Debugklasse vorstellen.. Eventuell kann sie ja mal jemand gebrauchen.. Da ich sie nach Monat langem 'Hin und wieder mal aendern' nun doch ganz gelungen finde (wenn auch etwas unuebersichtlich :/)
Jedenfalls der Inhalt der Klasse:
Zum Thema Konfiguration komme ich dann am Schluss.
Daraus ergibt sich folge Klassenuebersicht:
Fangen wir mal mit dem leichten an, dem Timetracker:
Genau genommen koennte man den auch in eine Funktion zusammenfassen. Der Unterschied zwischen start() und microtime() ist lediglich der Text, der am Ende ausgegeben wird. Bei stop() kommt dann noch ein 2 Parameter dazu, der sagt ob es gleich ausgegeben werden soll, oder durch spaeteren Aufruf der printTime()-Methode (oder eben gar nicht). Ich erklaere hier mal nicht sonderlich viel, dass meiste sollte klar sein - ein paar Kommentare sind ja da.
Beispielausgabe:
'test2' wird nicht ausgegeben, da ich beim stop() im 2. Parameter ein false habe - ich koennte jetzt also Debug:
rintTime('test2') benutzen um es anzuzeigen.
Der run "page" wird in der initialize-Methode gestartet (in der config ausstellbar)
Damit kommen wir dann auch schon zum Backtrace:
Hierfuer brauchen wir 2 Funktionen - eine Funktion zum anzeigen der Parameter die in den Funktionen vom Backtrace angezeigt werden, und eine zum auswerten des Backtrace. Man siehe wieder die Kommentare.
Wieder ein kleiner Beispielaufruf:
Jetzt fehlen noch die Funktionen fuer die Variablen ausgabe:
Dafuer haben wir dann gleich 5 Funktionen die das steuern:
In der 5. habe ich bei manchen stellen Kommentare eingefuegt, wo ich dachte da machen sie Sinn.. Sollte es genauer gewuenscht sein hole ich das noch nach, aber achtet auf die Uhrzeit :/
Beispiel:
Ausgabe diesmal als Bild:
Bleiben nur noch die initialize und end Funktionen:
Ach, und die debug.config.php natuerlich:
wollte nur mal meine 'neue' Debugklasse vorstellen.. Eventuell kann sie ja mal jemand gebrauchen.. Da ich sie nach Monat langem 'Hin und wieder mal aendern' nun doch ganz gelungen finde (wenn auch etwas unuebersichtlich :/)
Jedenfalls der Inhalt der Klasse:
- Backtrace als String (.. Wann wurde welche Funktion bis zu dieser aufgerufen)
- Tracking 'der Zeit' mit 3 Methoden: start (Anfang), microtime (Zwischenschritte), stop (Ende)
- Ausgabe von Variablen
Zum Thema Konfiguration komme ich dann am Schluss.
Daraus ergibt sich folge Klassenuebersicht:
PHP:
<?php
class Debug
{
private static $microtime = array();
private static $config = array();
private static $objectsDumped = array();
private static $log = null;
public static function initialize()
{ }
public static function getArgs($args)
{ }
public static function getBacktrace($lastOnly = false, $slice = 0, $showFunction = true)
{ }
public static function microtime($key = 'undefined')
{ }
public static function start($key = 'undefined')
{ }
public static function stop($key = 'undefined', $echo = true)
{ }
public static function createTime($end, $start, $deci)
{ }
public static function printTime($key = '')
{ }
public static function dump()
{ }
public static function _getVar($var)
{ }
private static function objectIsDumped($object)
{ }
private static function removeDumpedObject($object)
{ }
private static function doDump(&$var, $varName = NULL, $indent = false, $reference = NULL)
{ }
public static function end()
{ }
}
Fangen wir mal mit dem leichten an, dem Timetracker:
Genau genommen koennte man den auch in eine Funktion zusammenfassen. Der Unterschied zwischen start() und microtime() ist lediglich der Text, der am Ende ausgegeben wird. Bei stop() kommt dann noch ein 2 Parameter dazu, der sagt ob es gleich ausgegeben werden soll, oder durch spaeteren Aufruf der printTime()-Methode (oder eben gar nicht). Ich erklaere hier mal nicht sonderlich viel, dass meiste sollte klar sein - ein paar Kommentare sind ja da.
PHP:
public static function microtime($key = 'undefined')
{
// wurde in der Config eingestellt, dass der Aufruf angezeigt werden soll?
// also z.B. ich rufe in der index.php in der Zeile 18 die Methode Debug::microtime() auf - das wird dann angezeigt.
$backtrace = self::$config['TIME_SHOW_CALL'] == true ? ' » ' . self::getBacktrace(true, 1, false) : '';
// Den Standard Output mit dem Backtrace zusammensetzen
$output = '<br />Run "' . $key . '"' . $backtrace;
// alles erst mal zwischenspeichern
self::$microtime[$key][] = array('output' => $output, 'time' => microtime(true));
}
public static function start($key = 'undefined')
{
// Hier wird dann erst mal geguckt ob der uebergebene key schon im tracking ist - wenn nicht siehe microtime() - wenn ja wird microtime() aufgerufen.
if (!isset(self::$microtime[$key]))
{
$backtrace = self::$config['TIME_SHOW_CALL'] == true ? ' » ' . self::getBacktrace(true, 1, false) : '';
$output = '<br />Start run "' . $key . '"' . $backtrace;
self::$microtime[$key][] = array('output' => $output, 'time' => microtime(true));
}
else
{
self::microtime($key);
}
}
public static function stop($key = 'undefined', $echo = true)
{
$backtrace = self::$config['TIME_SHOW_CALL'] == true ? ' » ' . self::getBacktrace(true, 1, false) : '';
$output = '<br />Stop run "' . $key . '"' . $backtrace;
self::$microtime[$key][] = array('output' => $output, 'time' => microtime(true));
if ($echo)
{
self::printTime($key);
}
}
// Die Methode ist hier eigentlich unnoetig, da ich bei mir aber BCMath drin habe und eine Bedingung,
// ob die Funktionen von BCMath aufrufbar sind oder nicht (wenn nicht wird nur number_format genommen).
// Da ich aber (leider) feststellen musste, dass kaum ein Hoster BCMath installiert, habe ich das hier weg gelassen.
public static function createTime($end, $start, $deci) {
return number_format($end-$start, $deci);
}
// Die Methode ist - wie der Name schon sagt - fuer das ausgeben der getrackten Zeit da.
// Der Aufruf ist einfach nur Debug::printTime() - dann werden alle Eintraege ausgegeben
// oder Debug::printTime($key) - dann wird nur dieser ausgegeben
public static function printTime($key = '')
{
$time = '<div style="text-align: left;">';
if ($key == '')
{
foreach (self::$microtime as $current_key => $value)
{
self::printTime($current_key);
}
}
elseif(isset(self::$microtime[$key]))
{
$run = self::$microtime[$key];
$end = count(self::$microtime[$key])-1;
// Den Output von (optimal) Debug::start('xy'); ausgeben
$time .= $run[0]['output'] . "\n<br />    0.0000 seconds";
for($i = 1; $i < $end; $i ++)
{
// Den Output von den Zwischenschritten + zusaetzlich gebrauchte Zeit ausgben
$time .= $run[$i]['output'] . "\n<br />    + " . self::createTime($run[$i]['time'], $run[$i - 1]['time'], 4) . ' seconds';
}
// Den Output von (optimal) Debug::stop('xy'); ausgeben und die gesamte gebrauchte Zeit ausrechnen
$time .= $run[$end]['output'] . "\n<br />    + " . self::createTime($run[$end]['time'], $run[$end - 1]['time'], 4) . ' seconds' .
"\n<br />    = " . self::createTime($run[$end]['time'], $run[0]['time'], 4) . ' seconds';
}
else
{
$time = "Run '{$key}' does not exist.";
}
$time .= '</div>';
// Es wird geschaut, ob in der Config steht, dass Ausgegebene Runs geloescht werden sollen
if(self::$config['TIME_REMOVE_PRINTED'])
{
unset(self::$microtime[$key]);
}
echo $time;
}
Beispielausgabe:
PHP:
<?php
include 'debug/debug.php';
Debug::initialize();
Debug::start('test1');
sleep(3);
Debug::microtime('test1');
Debug::start('test2');
sleep(4);
Debug::stop('test2', false);
Debug::microtime('test1');
sleep(2);
Debug::stop('test1');
Code:
Start run "test1" » test/index.php:5
0.0000 seconds
Run "test1" » test/index.php:8
+ 3.0003 seconds
Run "test1" » test/index.php:13
+ 4.0003 seconds
Stop run "test1" » test/index.php:16
+ 2.0002 seconds
= 9.0008 seconds
Start run "page" » test/debug/debug.php:21
0.0000 seconds
Stop run "page" » test/debug/debug.php:366
+ 9.0011 seconds
= 9.0011 seconds
rintTime('test2') benutzen um es anzuzeigen.Der run "page" wird in der initialize-Methode gestartet (in der config ausstellbar)
Damit kommen wir dann auch schon zum Backtrace:
Hierfuer brauchen wir 2 Funktionen - eine Funktion zum anzeigen der Parameter die in den Funktionen vom Backtrace angezeigt werden, und eine zum auswerten des Backtrace. Man siehe wieder die Kommentare.
PHP:
// Hier wird quasi nur eins gemacht: Es werden die Parameter durchlaufen und die Variable $return mit den Inhalten gefuellt.
public static function getArgs($args)
{
$return = '';
foreach ($args as $arg)
{
if (is_object($arg))
{
$return .= 'object(' . get_class($arg) . '), ';
}
elseif (is_string($arg))
{
$arg = htmlspecialchars($arg);
$return .= '"' . $arg . '", ';
}
elseif (is_int($arg) || is_double($arg))
{
$return .= $arg . ', ';
}
elseif (is_null($arg))
{
$return .= 'NULL, ';
}
elseif (is_bool($arg))
{
$return .= ($arg ? 'true' : 'false') . ', ';
}
elseif (is_array($arg))
{
$return .= 'Array (' . count($arg) . '), ';
// Einkommentiert wegen Recursionsgefahr
// ' { ';
// foreach ($arg as $key => $value)
// {
// if ($key !== 'GLOBALS' && $key !== '_GET' && $key !== '_POST' && $key !== '_SESSION' && $key !== '_COOKIE' && $key !== '_SERVER' && $key !== '_FILES')
// {
// $return .= ' ' . $key . ' => ' . self::getArgs(array($value));
// }
// }
// unset($key, $value);
// $return .= '} , ';
}
else
{
$return .= $arg . ', ';
}
}
$return = substr($return, 0, - 2);
return $return;
}
// Parameter 1 sagt, ob nur die letzte Funktion angezeigt werden soll oder der komplette Backtrace.
// Parameter 2 gibt den slice an, also wie viele Funktionen uebersprungen werden sollen. z.B. ich rufe in der Funktion x die Funktion y auf.
* in Funktion y hole ich mir dann den backtrace, dieser soll die Funktion y aber nicht beinhalten, also gebe ich als slice 1 an.
// Parameter 3 gibt an, ob die Klasse & Funktion mit ausgegeben werden soll. Bei false wird nur die Datei:Zeile ausgegeben
public static function getBacktrace($lastOnly = false, $slice = 0, $showFunction = true)
{
$return = array();
$backtrace = debug_backtrace(false);
// 1 backtrace loeschen, dieser ist dann der Aufruf von Debug::getBacktrace
* ich denke, den will keiner. Und es gibt dann Probleme mit dem slice sollter er drin bleiben..
* (Man muesste immer +1 auf die Zahl rechnen)
array_shift($backtrace);
$disabledFunctions = array('include', 'include_once', 'require', 'require_once', 'getBacktrace');
$count = count($backtrace);
for (; $slice < $count; $slice++)
{
if (!in_array($backtrace[$slice]['function'], $disabledFunctions) && isset($backtrace[$slice]['file']))
{
$currentBacktrace = $backtrace[$slice];
$file = str_replace(self::$config['DOCUMENT_ROOT'], '', $currentBacktrace['file']);
$line = $currentBacktrace["line"];
if ($showFunction)
{
$class = '<br /> ';
$class .= (isset($currentBacktrace["class"])) ? $currentBacktrace["class"] : '';
$type = (isset($currentBacktrace["type"])) ? $currentBacktrace["type"] : '';
$args = !empty($currentBacktrace['args']) ? self::getArgs($currentBacktrace['args']) : '';
$function = $currentBacktrace["function"] . '(' . $args . ')';
}
else
{
$class = $type = $function = '';
}
$return[] = sprintf("%s:%s %s%s%s", $file, $line, $class, $type, $function);
if ($lastOnly)
{
break;
}
}
}
$return = implode('<br />' . PHP_EOL, $return);
return $return;
}
Wieder ein kleiner Beispielaufruf:
PHP:
<?php
include 'debug/debug.php';
Debug::initialize();
function x($func)
{
$func();
}
function pur()
{
echo Debug::getBacktrace();
}
function slice()
{
echo Debug::getBacktrace(false, 1);
}
function last()
{
echo Debug::getBacktrace(true);
}
function noFunc()
{
echo Debug::getBacktrace(false, 0, false);
}
class test
{
function myTest()
{
echo Debug::getBacktrace();
}
}
echo "pur:<br />";
x('pur');
echo "<br /><br />slice:<br />";
x('slice');
echo "<br /><br />last:<br />";
x('last');
echo "<br /><br />noFunc:<br />";
x('noFunc');
echo "<br /><br />Klassen:<br />";
$test = new test();
$test->myTest();
echo '<br /><br />';
Test::myTest();
Code:
pur:
test/index.php:7
pur()
test/index.php:40
x("pur")
slice:
test/index.php:43
x("slice")
last:
test/index.php:7
last()
noFunc:
test/index.php:7
test/index.php:49
Klassen:
test/index.php:53
test->myTest()
test/index.php:55
test::myTest()
Jetzt fehlen noch die Funktionen fuer die Variablen ausgabe:
Dafuer haben wir dann gleich 5 Funktionen die das steuern:
- dump() - ist quasi nur ein wrapper, damit man mehrere Variablen auf einmal ausgeben kann.
- _getVar() - bereitet das ausgeben vor, also setzt den div-Container mit dem style, sorgt fuer die 'called in'-Ausgabe.. etc.
- objectIsDumped() - prueft ob ein Object schonmal ausgegeben wurde (sonst recursion bei Referenzen ..)
- removeDumpedObject() - loescht das Object wieder
- INFO: objectIsDumped und removeDumpedObject saugen ziemlich an der Performance, da das komplette Object durch serialize() und md5() gezogen wird - damit das ganze genau bleibt. Man koennte auch den Namen des Objects/Klasse uebergeben, waere auf jedenfall schneller - aber halt auch ungenauer
- und die 5. Funktion: doDump() die geht recursiv durch die Variable und gibt alle Inhalte aus.
In der 5. habe ich bei manchen stellen Kommentare eingefuegt, wo ich dachte da machen sie Sinn.. Sollte es genauer gewuenscht sein hole ich das noch nach, aber achtet auf die Uhrzeit :/
PHP:
public static function dump()
{
$return = '';
$stack = debug_backtrace();
if (isset($stack[0]["args"]))
{
for ($i = 0; $i < count($stack[0]["args"]); $i ++)
{
$return .= self::_getVar($stack[0]["args"][$i]);
}
}
echo $return;
return $return;
}
public static function _getVar($var)
{
$return = '<div style="' . self::$config['DUMP_STYLE'] . '">';
if(self::$config['DUMP_SHOW_CALL'])
{
$return .= '<div>Called in: ' . self::getBacktrace(true, 1, false) . '</div>';
}
$return .= self::doDump($var);
$return .= "</div>";
return $return;
}
private static function objectIsDumped($object)
{
$objectHash = md5(serialize($object));
$dumped = isset(self::$objectsDumped[$objectHash]);
self::$objectsDumped[$objectHash] = true;
return $dumped;
}
private static function removeDumpedObject($object)
{
$objectHash = md5(serialize($object));
unset(self::$objectsDumped[$objectHash]);
}
/*
* Urspruengliche Vorlage dieser Funktion: @http://de3.php.net/manual/de/function.var-dump.php#80288
*/
private static function doDump(&$var, $varName = NULL, $indent = false, $reference = NULL)
{
$color = self::$config['DUMP_COLOR'];
$return = '';
if ($indent === true)
{
$return = '<div style="margin-left: ' . self::$config['DUMP_MARGIN'] . ';">';
}
if (empty($varName))
{
$printVarName = $varName = '';
}
else
{
$printVarName = $varName . ' = ';
}
$reference = $reference . $varName;
$protectionVar = 'the_do_dump_recursion_protection_scheme';
$protectionName = 'referenced_object_name';
$type = gettype($var);
switch($type)
{
case 'array':
if(isset($var[$protectionVar]))
{ // diese bedingungist nur wahr, wenn das Referenzierte Array schon mal aufgerufen wird - da dieses erst im 'else'-Zweig umgebaut wird.
$realName = $var[$protectionName];
$return .= $varName . ' => <span style="color:' . $color['TYPE'] . ';"> ' . gettype($var[$protectionVar]) . '</span> <span style="color:' . $color['RECURSION'] . ';">&' . $realName . ' (*RECURSION*)</span>' . WRITELN;
}
else
{
// jetzt wird die Variable fuer die Bedingung oben umgebaut
$var = array($protectionVar => $var, $protectionName => $reference);
$array = &$var[$protectionVar];
$count = count($array);
if ($count > 0)
{
$return .= $printVarName . '<span style="color:' . $color['TYPE'] . ';">array (' . $count . ')</span> (' . WRITELN;
$keys = array_keys($array);
foreach ($keys as $keyName)
{
$value = &$array[$keyName];
$return .= self::doDump($value, '[' . (is_int($keyName) ? $keyName : '"' . $keyName . '"') . ']', true, $reference);
}
unset($keyName);
$return .= ')' . WRITELN;
}
else
{
$return .= $printVarName . '<span style="color:' . $color['TYPE'] . ';">array (0)</span> ' . WRITELN;
}
// Schleife ist hier vorbei - Variable wieder zurueckbauen
$var = $var[$protectionVar];
}
break;
case 'object':
if(self::objectIsDumped($var)) // Pruefen ob Object schonmal inneralb dieser Schleife ausgegeben wurde (speichert automatisch die Variable im Recusionschutz) - wenn ja recursion ausgeben
{
$return .= $varName . ' => <span style="color:' . $color['TYPE'] . ';"> object</span> <span style="color:' . $color['RECURSION'] . ';">&' . get_class($var) . ' (*RECURSION*)</span>' . WRITELN;
}
else
{
$className = get_class($var);
$return .= $printVarName . '<span style="color:' . $color['TYPE'] . ';">object (' . $className . ')</span> (' . WRITELN;
$valueArray = (array) $var; // Object in ein Array umwandeln, da man sonst nicht an die protected/private Variablen rankommt
$keys = array_keys($valueArray); // Variablennamen holen
foreach ($keys as $keyName)
{
if (preg_match('#\0(' . $className . '|\*)\0(.*)#', $keyName, $match)) // RegEx-Befehl zum auslesen der Sichtbarkeit von der Variable
{
$keyRealName = $match[2];
if ($match[1] == '*')
{
$mode = 'protected';
}
else
{
$mode = 'private';
}
}
else
{
$mode = 'public';
$keyRealName = $keyName;
}
$value = $valueArray[$keyName];
$return .= self::doDump($value, '[' . (is_int($keyRealName) ? $keyRealName : '"' . $keyRealName . '" : ' . $mode) . ']', true, $reference);
}
unset($keyName);
$return .= ')' . WRITELN;
self::removeDumpedObject($var); // Schleife vorbei - recursionsschutz entfernen
}
break;
case 'integer':
case 'double':
case 'float':
$return .= sprintf('%s<span style="color:' . $color['TYPE'] . ';">(%s)</span> <span style="color:' . $color[strtoupper($type)] . ';">%s</span>' . WRITELN, $printVarName, $type, $var);
break;
case 'string':
$return .= sprintf('%s<span style="color:' . $color['TYPE'] . ';">(string - %d)</span> <span style="color:' . $color['STRING'] . ';">"%s"</span>' . WRITELN, $printVarName, strlen($var), htmlspecialchars($var));
break;
case 'boolean':
$return .= sprintf('%s<span style="color:' . $color['TYPE'] . ';">(boolean)</span> <span style="color:' . $color['BOOLEAN'] . ';">%s</span>' . WRITELN, $printVarName, ($var ? "true" : "false"));
break;
case 'NULL':
$return .= sprintf('%s<span style="color:' . $color['NULL'] . ';">NULL</span>' . WRITELN, $printVarName);
break;
case 'resource':
$return .= sprintf('%s<span style="color:' . $color['TYPE'] . ';">(resource)</span> <span style="color:' . $color['RESOURCE'] . ';">%s</span> . WRITELN', $printVarName, $var);
break;
default:
trigger_error("Unknown variable type ({$type}) in Debug::Dump");
}
if ($indent === true)
{
$return .= '</div>';
}
return $return;
}
PHP:
<?php
include 'debug/debug.php';
Debug::initialize();
class Test
{
private $self;
protected $protected;
public $public;
function Test()
{
$this->self = $this;
}
}
$array = array('test' => 'testValue', 'testIndex');
$array['recursion'] = &$array;
$array2 = array('test2' => 'test2Value');
$array2['recursion2'] = &$array;
Debug::dump($array2);
$test = new Test();
$obj1 = new stdClass();
$obj1->obj = $test;
Debug::dump($obj1);
Debug::dump($test);
Bleiben nur noch die initialize und end Funktionen:
PHP:
public static function initialize()
{
include __DIR__ . '/debug.config.php';
self::$config = $config;
define($config['DEFINE_DEBUG_KEY'], true);
if(!defined('WRITELN')) {
define('WRITELN', '<br />' . PHP_EOL);
}
if($config['TIME_AUTOSTART'])
{
self::start('page');
}
register_shutdown_function('Debug::end');
}
public static function end()
{
if (isset(self::$microtime['page']))
{
self::stop('page');
}
}
Ach, und die debug.config.php natuerlich:
PHP:
<?php
/* DEBUG CONFIG */
$config = array();
$config['DEFINE_DEBUG_KEY'] = 'DEBUG';
$config['DOCUMENT_ROOT'] = $_SERVER['DOCUMENT_ROOT'];
/* Debug::Dump() config */
// show the backtrace in Debug::Dump()
$config['DUMP_SHOW_CALL'] = true;
// indent in Arrays, Objects
$config['DUMP_MARGIN'] = '10px';
// default style for dumps
$config['DUMP_STYLE'] = 'margin-bottom: 10px; background: #FFFFFF; font-family: Verdana; color: #000000; border: 1px solid #cccccc; padding: 5px; font-size: 10px; line-height: 13px;text-align: left;';
// color for Var types
$config['DUMP_COLOR'] = array();
$config['DUMP_COLOR']['TYPE'] = '#A2A2A2';
$config['DUMP_COLOR']['INTEGER'] = '#FF0000';
$config['DUMP_COLOR']['FLOAT'] = '#0099c5';
$config['DUMP_COLOR']['DOUBLE'] = '#0099c5';
$config['DUMP_COLOR']['STRING'] = 'green';
$config['DUMP_COLOR']['BOOLEAN'] = '#92008D';
$config['DUMP_COLOR']['NULL'] = '#0000FF';
$config['DUMP_COLOR']['RESOURCE'] = 'black';
$config['DUMP_COLOR']['RECURSION'] = '#e87800';
/* Debug::start(), ::microtime(), ::stop() config */
// show backtrace in Debug::start(), ::microtime(), ::stop()
$config['TIME_SHOW_CALL'] = true;
// start/end tracking 'page'
$config['TIME_AUTOSTART'] = false;
// remove tracking after echo
$config['TIME_REMOVE_PRINTED'] = true;
Zuletzt bearbeitet: