読者です 読者をやめる 読者になる 読者になる

PHP5でオブジェクトダンプ可能なデバッガ

20070721--Toytools_Debug_Core.phpにバグがあったのでソースを修正しました


デバッギングトレースはバックトレースなど色々方法はあるけど、僕がやっている方法を少し紹介
ページの最後にこんな形でダンプのトレースが表示できます(PHP5以上)


基本的な機能としては

  • デバッグレベルでのトレース切り替え(Log4Jっぽく)
  • 内容を自動でページの最後に出力
  • クラスやException、arrayなどの場合には再帰的に内容を整形してダンプ

などなど。

使い方

※本体のソースは下のほうにあります

<?php
require_once 'Toytools/Debug.php';
//ロガーを作る
$envLog = new Toytools_Debug( 'ENV' );
//ダンプ。第二引数はオプション【POST @ENV】のような名前になります
$envLog->debug( $_POST ,'POST' );
//ダンプ。この場合は【ENV】のような名前になります
$envLog->debug( $_POST );
?>

デバッグレベルの変更

<?php
Toytools_Debug::setDebugLebelByName( 'INFO' );//文字列から
Toytools_Debug::setDebugLebel( Toytools_Debug::LEVEL_DEBUG );//定数から
?>

デフォルトはDEBUGレベルです。
DEBUG > INFO > WARN > ERROR > FATAL > NONE

こんな方法も使えます

<?php
require_once 'Toytools/Debug.php';
class Test{
	/**
	 * @var Toytools_Debug
	 */
	private $_log;
	function __construct(){
		$this->_log = new Toytools_Debug(__CLASS__);
		$this->_log->debug( 'テスト' , __FUNCTION__ );
	}
	
	function hoge(){
		$this->_log->debug( 'テスト' , __FUNCTION__ );
	}
}
?>

__CLASS__や__FUNCTION__といった定数を利用してあげることでデバッグに必要な情報を付与します
__FILE__、__LINE__なども必要に応じて出力してあげると便利です



ソース

Toytools/Debug.php

<?php
require_once 'Toytools/Debug/Core.php';
/**
 * Debug Logger
 * @author kaw
 * @package Toytools
 * @usage
 * 			$debugger = new Toytools_Debug_Debug( 'TEST' );
 * 			$debugger->debug( $mixedValue );	
 */
class Toytools_Debug{
	const LEVEL_DEBUG = 4;// アプリケーションのデバッグでもっとも役立つ細粒度の情報イベントを指します。 
	const LEVEL_INFO  = 3;// 粗粒度レベルでアプリケーションの推移を強調する情報のメッセージを指します。 
	const LEVEL_WARN  = 2;// 潜在的に、有害な状況を指します。 
	const LEVEL_ERROR = 1;// まだアプリケーションが起動しつづけられるようなエラーイベントを指します。
	const LEVEL_FATAL = 0;// おそらくアプリケーションの停止を起こすようなまさにサーバのエラーイベントを指します。
	const LEVEL_NONE  = -1;// ログに何も記録しません。
	
	var $debugName;
	static $level = self::LEVEL_DEBUG;
	
	
	/**
	 * CONSTRUCT
	 * @param	ロガー名
	 */
	function __construct( $debugName = null ){
		Toytools_Debug_Core::initialize();
		$this->debugName = $debugName;
	}
	
	/**
	 * ロガー名を取得する
	 * @return string ロガー名
	 */
	public function getDebugName($name = null){
		if( !is_null($name) ){
			return $name.' @'.$this->debugName;
		}
		return $this->debugName;
	}
	
	/**
	 * デバッグレベルを設定する
	 * @access public static
	 * @usage
	 * 			Toytools_Debug::setDebugLebel( Toytools_Debug::LEVEL_DEBUG );
	 */
	public static function setDebugLebel( $debugLevel ){
		if( !is_int($debugLevel) ){
			throw new Error('Toytools_Debug::setDebugLebel argument need to Integer');
		}
		self::$level = $debugLevel;
	}
	
	
	public static function setDebugLebelByName( $debugLevelName ){
		$debugLevelName = strtoupper($debugLevelName);
		switch( $debugLevelName ){
			case 'DEBUG':
				$debugLevel = self::LEVEL_DEBUG;
				break;
			case 'INFO':
				$debugLevel = self::LEVEL_INFO;
				break;
			case 'WARN':
				$debugLevel = self::LEVEL_WARN;
				break;
			case 'ERROR':
				$debugLevel = self::LEVEL_ERROR;
				break;
			case 'FATAL':
				$debugLevel = self::LEVEL_FATAL;
				break;
			case 'NONE':
				$debugLevel = self::LEVEL_NONE;
				break;
			default:
				throw new Error('Toytools_Debug::setDebugLebelByName undefined debug lebel name');
				break;
		}
		self::setDebugLebel($debugLevel);
	}
	
	
	//---------------------------------------------------------------------
	
	
	/**
	 * とりあえずトレース(デバッグレベル扱いになります)
	 * @param	トレースする値
	 * @param	デバッグ名
	 */
	public static function trace( $value , $debugName = null ){
		if( self::$level >= self::LEVEL_DEBUG ){
			Toytools_Debug_Core::dump( $value , $debugName );
		}
	}
	
	
	
	//------ レベル別 ---------------------------------------------------------------
	/**
	 * DEBUGレベルトレース
	 * @param	mixed
	 */
	public function debug( $value , $name = null ){
		if( self::$level >= self::LEVEL_DEBUG ){
			Toytools_Debug_Core::dump( $value , $this->getDebugName($name) , self::LEVEL_DEBUG );
		}
	}
	
	/**
	 * INFOレベルトレース
	 * @param	mixed
	 */
	public function info( $value , $name = null ){
		if( self::$level >= self::LEVEL_INFO ){
			Toytools_Debug_Core::dump( $value , $this->getDebugName($name) , self::LEVEL_INFO );
		}
	}
	
	/**
	 * WARNレベルトレース
	 * @param	mixed
	 */
	public function warn( $value , $name = null ){
		if( self::$level >= self::LEVEL_WARN ){
			Toytools_Debug_Core::dump( $value , $this->getDebugName($name) , self::LEVEL_WARN );
		}
	}
	
	/**
	 * ERRORレベルトレース
	 * @param	mixed
	 */
	public function error( $value , $name = null ){
		if( self::$level >= self::LEVEL_ERROR ){
			Toytools_Debug_Core::dump( $value , $this->getDebugName($name) , self::LEVEL_ERROR );
		}
	}
	
	/**
	 * FATALレベルトレース
	 * @param	mixed
	 */
	public function fatal( $value , $name = null ){
		if( self::$level >= self::LEVEL_FATAL ){
			Toytools_Debug_Core::dump( $value , $this->getDebugName($name) , self::LEVEL_FATAL );
		}
	}
	
}
?>

Toytools/Debug/Core.php

<?php
require_once 'Toytools/Debug.php';

/**
 * デバッグトレースのコア
 * abstractクラスにさせてそのうち継承させる??
 */
class Toytools_Debug_Core{
	private static $_cssEchoed      = false;
	private static $_initialized    = false;
	private static $_responseStack = '';
	private static $_responseTraceEnabled = true;
	
	
	/**
	 * 初期化をします
	 * このメソッドをコールすることによってページ終了時にスタックに溜まっている情報を最後に自動的に出力します
	 * シングルトンにしてデストラクタ使うのもありかも
	 */
	public static function initialize(){
		if( self::$_initialized ){
			return;
		}
		register_shutdown_function(array('Toytools_Debug_Core', "_flushStack"));//REGIST STATIC METHOD
		self::$_initialized = true;
	}
	
	
	
	/**
	 * Stackに溜まっているものを出力します
	 * @return void
	 */
	public static function _flushStack(){
		if( !self::$_responseTraceEnabled ){
			return;
		}
		self::_echoDebugCss();//CSSが未出力だったら出力
		if( self::$_responseStack == '' ){
			return;
		}
		echo '<br style="clear:both;margin-top:50px;" /><hr />';
		echo self::$_responseStack;
		self::$_responseStack = '';
	}
	
	public static function setNonTrace(){
		self::$_responseTraceEnabled = false;
	}
	
	
	/**
	 * 値をスタックに追加します
	 * インターフェイスとしてDebugクラスから必要なものはこれだけ
	 * 拡張するときにはこれだけ定義したInterfaceかAbstractクラスつくるかな
	 * @param	mixed $mixedValue
	 * @param	string|int|null ロガー名
	 * @param	int デバッグレベル
	 * @param	Divをトレースするか(基本的には指定しないでください)
	 */
	public static  function dump( $mixedValue , $loggerName = null , $debugLevel = null , $isEchoDiv = true , $traceObjectMethods = true ){
		//self::_echoDebugCss();//CSSが未出力だったら出力
		
		if( $isEchoDiv ){
			self::_pushResponseStack( '<div class="toydebug">' );
		}
		if( !is_null($debugLevel) ){
			$level = self::_getDebugLevelLabel( $debugLevel );
			self::_pushResponseStack( $level );
		}
		if( !is_null($loggerName) ){
			self::_pushResponseStack( '<b>【'.$loggerName.'】 </b>' );
		}
		switch( true ){
			//STRING
			case is_string($mixedValue):
				self::_pushResponseStack( self::_getTypeSpan('STRING').nl2br(htmlspecialchars($mixedValue)) );
				break;
			//INT
			case is_int($mixedValue):
				self::_pushResponseStack( self::_getTypeSpan('INT').$mixedValue );
				break;
			//FLOAT
			case is_float($mixedValue):
				self::_pushResponseStack( self::_getTypeSpan('FLOAT').$mixedValue );
				break;
			//BOOL
			case is_bool($mixedValue):
				$mixedValue ? $mixedValue = 'TRUE' : $mixedValue = 'FALSE';
				self::_pushResponseStack( self::_getTypeSpan('BOOL').$mixedValue );
				break;
			//NULL
			case is_null($mixedValue):
				self::_pushResponseStack( 'NULL' );
				break;
			//ARRAY
			case is_array($mixedValue):
				self::_pushResponseStack( self::_getTypeSpan('ARRAY') );
				self::_pushResponseStack( '<ul>' );
				foreach ($mixedValue as $key => $value) {
					self::_pushResponseStack( '<li>'.self::_getListKeySpan($key).' ' );
					self::dump($value,null,null,( is_array($value) || is_object($value) ),$traceObjectMethods);
					self::_pushResponseStack( '</li>' );
				}
				self::_pushResponseStack( '</ul>' );
				break;
			//Exception
			case ( $mixedValue instanceof Exception ):
				self::_pushResponseStack( '<span class="toydebug_color_exception">'.get_class($mixedValue).'</span>' );
				self::_pushResponseStack( '<ul>' );
				
				
				self::_pushResponseStack( '<li><span class="toydebug_color_exception_item"> MESSAGE </span> '.htmlspecialchars($mixedValue->getMessage()).'</li>' );
				self::_pushResponseStack( '<li><span class="toydebug_color_exception_item"> FILE </span> '.htmlspecialchars($mixedValue->getFile()).' ( LINE:'.$mixedValue->getLine().' )</li>' );
				self::_pushResponseStack( '<li><span class="toydebug_color_exception_item"> TRACE </span> ' );
					self::dump($mixedValue->getTrace(),null,null,true,false);
				self::_pushResponseStack( '</li>' );
				self::_pushResponseStack( '</ul>' );
				break;
			//OBJECT
			case is_object($mixedValue):
				$className = get_class($mixedValue);
				self::_pushResponseStack( 'CLASS :: '.$className );
				if( $traceObjectMethods ){
					$methods = get_class_methods($mixedValue);
					if( !is_null($methods) ){
						self::_pushResponseStack( '<ul>' );
						foreach ($methods as $method_name) {
						    self::_pushResponseStack( '<li>[METHOD] '.htmlspecialchars($method_name).'</li>' );
						}
						self::_pushResponseStack( '</ul>' );
					}
				}
				$vars    = get_object_vars($mixedValue);
				if( !is_null($vars) ){
					self::_pushResponseStack( '<ul>' );
					foreach ($vars as $key => $value) {
						if( !is_object( $value ) ){
							self::_pushResponseStack( '<li>[VARS] $'.htmlspecialchars($key.''.$value).'</li>' );
						}else{
							self::dump($value,$key,null,true,false);
						}
					}
					self::_pushResponseStack( '</ul>' );
				}
				break;
			//OTHER TYPE
			default:
				self::_pushResponseStack( htmlspecialchars($mixedValue) );
				break;
			
		}
		if( $isEchoDiv ){
			self::_pushResponseStack( '</div>' );
		}
	}
	

	/**
	 * Debug用のCSSを出力します
	 * @return void
	 */
	private static function _echoDebugCss(){
		if( self::$_cssEchoed ){
			return;
		}
		echo '
<style type="text/css">
<!--
div.toydebug {
	margin:5px;
	margin-left:30px;
	background-color:#FFFFCC;
	border:solid 1px #666666;
	padding:3px;
}
div.toydebug ul{
	margin-left:0px;
}
div.toydebug li{
	list-style-type:none;
	margin-left:0px;
}
div.toydebug span.toydebug_type{
	color:#3366FF;
}
div.toydebug span.toydebug_list_key{
	color:#FF0000;
}
.toydebug_color_debug{
	background-color:#339900;
	color:#FFFFFF;
	font-weight:bold;
	width:80px;
}
.toydebug_color_info{
	background-color:#0066FF;
	color:#FFFFFF;
	font-weight:bold;
	width:80px;
}
.toydebug_color_warn{
	background-color:#FF0000;
	color:#FFFFFF;
	font-weight:bold;
	width:80px;
}
.toydebug_color_fatal{
	background-color:#FF0000;
	color:#FFFFFF;
	font-weight:bold;
	width:80px;
}
.toydebug_color_error{
	background-color:#FF0000;
	color:#FFFFFF;
	font-weight:bold;
	width:80px;
}
.toydebug_color_exception{
	background-color:#FF0000;
	color:#FFFFFF;
	font-weight:bold;
	min-width:80px;
}
.toydebug_color_exception_item{
	background-color:#666699;
	color:#FFFFFF;
	font-weight:bold;
	min-width:80px;

}
-->
</style>';
		self::$_cssEchoed = true;
	}
	
	
	/**
	 * Stackに追加します
	 * @param mixed $pushStr
	 */
	private static function _pushResponseStack( $pushStr ){
		self::$_responseStack .= $pushStr;
	}
	
	
	/**
	 * デバッグレベルの表示用ラベルを取得します
	 * @param int $debugLevel
	 * @return string
	 */
	private static function _getDebugLevelLabel( $debugLevel ){
		switch( $debugLevel ){
			case Toytools_Debug::LEVEL_DEBUG;
				$cssStyleClass = 'toydebug_color_debug';
				$levelLabel    = 'DEBUG';
				break;
			case Toytools_Debug::LEVEL_INFO;
				$cssStyleClass = 'toydebug_color_info';
				$levelLabel    = 'INFO ';
				break;
			case Toytools_Debug::LEVEL_WARN;
				$cssStyleClass = 'toydebug_color_warn';
				$levelLabel    = 'WARN ';
				break;
			case Toytools_Debug::LEVEL_ERROR;
				$cssStyleClass = 'toydebug_color_fatal';
				$levelLabel    = 'ERROR';
				break;
			case Toytools_Debug::LEVEL_FATAL;
				$cssStyleClass = 'toydebug_color_error';
				$levelLabel    = 'FATAL';
				break;
			default:
				throw new Exception('Toytools_Debug_Core::_getLevelStyleClassName');
				break;
		}
		return '<span class="'.$cssStyleClass.'"> '.$levelLabel.' </span> ';
	}

	private static function _getTypeSpan( $typeName ){
		return '<span class="toydebug_type">['.$typeName.']</span> ';
	}
	private static function _getListKeySpan( $typeName ){
		return '<span class="toydebug_list_key">['.$typeName.']</span> ';
	}
}
?>