Created
September 25, 2012 11:09
-
-
Save k-holy/3781180 to your computer and use it in GitHub Desktop.
シャットダウン関数でエラー処理
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
php_value auto_prepend_file __prepend.php |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Acme; | |
class HttpException extends \Exception {}; | |
$logfile = __DIR__ . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'php_error.log'; | |
ob_start(); | |
$processor = new ErrorProcessor(array( | |
'logger' => function($message) use ($logfile) { | |
error_log(sprintf("[%s] %s\n", date('Y-m-d H:i:s'), $message), 3, $logfile); | |
}, | |
'log_level' => E_ALL, // ログは全てのエラーで取る | |
)); | |
set_error_handler(function($errno, $errstr, $errfile, $errline, $errcontext) use ($processor) | |
{ | |
$trace = debug_backtrace(); | |
$trace = (!empty($trace)) ? array_slice($trace, 1, count($trace)) : array(); | |
$processor->addError($errno, $errstr, $errfile, $errline, $trace); | |
// error_reportingに含まれないレベルのエラーはスルー | |
if (!(error_reporting() & $errno)) { | |
return true; | |
} | |
exit; | |
}); | |
set_exception_handler(function(\Exception $e) use ($processor) | |
{ | |
$processor->addException($e); | |
}); | |
register_shutdown_function(function() use ($processor) | |
{ | |
$buffered_content = ob_get_clean(); | |
// カスタムエラーハンドラでFalseを返した場合は、標準のエラー | |
// ハンドラで処理されすでに出力されたエラーもここで返される | |
$error = error_get_last(); | |
if (!empty($error)) { | |
$processor->addError( | |
$error['type'], | |
$error['message'], | |
$error['file'], | |
$error['line'] | |
); | |
} | |
// エラー発生時のステータスコードは display_errors に関わらず返す | |
if ($processor->hasError() && !headers_sent()) { | |
header($processor->statusLine()); | |
} | |
if (strlen($buffered_content) >= 1) { | |
echo $buffered_content; | |
} | |
if ($processor->hasError() && ini_get('display_errors')) { | |
echo $processor->content(); | |
exit; | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Acme; | |
class ErrorProcessor | |
{ | |
private $messages; | |
private $status; | |
private $log_level; | |
private $display_level; | |
private $logger; | |
private static $statuses = array( | |
400 => 'Bad Request', | |
403 => 'Forbidden', | |
404 => 'Not Found', | |
500 => 'Internal Server Error', | |
); | |
public function __construct(array $config = array()) | |
{ | |
$this->initialize($config); | |
} | |
/** | |
* オブジェクトを初期化します。 | |
* | |
* @param array 設定 | |
*/ | |
public function initialize(array $config = array()) | |
{ | |
$this->messages = array(); | |
$this->status = null; | |
if (isset($config['display_level'])) { | |
$this->display_level = $config['display_level']; | |
} | |
if (isset($config['log_level'])) { | |
$this->log_level = $config['log_level']; | |
} | |
if (isset($config['logger'])) { | |
$this->logger = $config['logger']; | |
} | |
return $this; | |
} | |
/** | |
* エラー/例外がセットされているかどうかを返します。 | |
* | |
* @return bool | |
*/ | |
public function hasError() | |
{ | |
return (count($this->messages) >= 1); | |
} | |
/** | |
* エラーを追加します。 | |
* display_level 設定値に含まれるエラーのみ出力対象とし、ステータスコードをセットします。 | |
* あらかじめ display_level を設定していない場合、実行時の error_reporting 設定値に従います。 | |
* | |
* @param int エラーレベル | |
* @param string エラーメッセージ | |
* @param string エラーが発生したファイル | |
* @param int エラーが発生した行番号 | |
* @return string エラーメッセージ | |
*/ | |
public function addError($errno, $errstr, $errfile, $errline, array $trace = array()) | |
{ | |
$message = $this->errorMessage( | |
$errno, | |
$errstr, | |
$errfile, | |
$errline, | |
$trace | |
); | |
$log_level = (isset($this->log_level)) ? $this->log_level : error_reporting(); | |
if ($log_level & $errno) { | |
$this->log($message); | |
} | |
$display_level = (isset($this->display_level)) ? $this->display_level : error_reporting(); | |
if ($display_level & $errno) { | |
$this->messages[] = $message; | |
$this->status = 500; | |
} | |
return $message; | |
} | |
/** | |
* 例外を追加し、ステータスコードをセットします。 | |
* 全ての例外は出力対象になります。 | |
* | |
* @param Exception | |
* @return string エラーメッセージ | |
*/ | |
public function addException(\Exception $exception) | |
{ | |
$message = $this->exceptionMessage($exception); | |
$this->log($message); | |
$this->messages[] = $message; | |
$this->status = ($exception instanceof HttpException) ? $exception->getCode() : 500; | |
return $message; | |
} | |
/** | |
* エラーのヘッダを生成する | |
* | |
* @param int エラーレベル | |
*/ | |
public function errorHeader($errno) | |
{ | |
$labels = array( | |
E_ERROR => 'Fatal error', | |
E_WARNING => 'Warning', | |
E_NOTICE => 'Notice', | |
E_STRICT => 'Strict standards', | |
E_RECOVERABLE_ERROR => 'Catchable fatal error', | |
E_DEPRECATED => 'Depricated', | |
E_USER_ERROR => 'User Fatal error', | |
E_USER_WARNING => 'User Warning', | |
E_USER_NOTICE => 'User Notice', | |
E_USER_DEPRECATED => 'User Depricated', | |
); | |
return sprintf('%s[%d]', isset($labels[$errno]) ? $labels[$errno] : '', $errno); | |
} | |
/** | |
* 例外のヘッダを生成する | |
* | |
* @param Exception | |
*/ | |
public function exceptionHeader(\Exception $e) | |
{ | |
return sprintf('Uncaught Exception %s[%d]', get_class($e), $e->getCode()); | |
} | |
/** | |
* エラー/例外メッセージを整形して返します。 | |
* | |
* @param string エラー/例外のヘッダ | |
* @param string エラー/例外のメッセージ | |
* @param string エラー/例外の発生したファイル | |
* @param int エラー/例外の発生した行 | |
* @param array スタックトレース | |
* @return string 整形したエラー/例外メッセージ | |
*/ | |
public function buildMessage($header, $message, $file, $line, array $trace = array()) | |
{ | |
return sprintf("%s: '%s' in %s on line %d%s", | |
$header, | |
$message, | |
$file, | |
$line, | |
(!empty($trace)) ? sprintf("\nStack trace:\n%s", implode("\n", self::formatTrace($trace))) : '' | |
); | |
} | |
/** | |
* 例外メッセージを整形して返します。 | |
* | |
* @param object \Exception 例外 | |
* @return string 整形した例外メッセージ | |
*/ | |
public function exceptionMessage(\Exception $exception) | |
{ | |
return $this->buildMessage( | |
$this->exceptionHeader($exception), | |
$exception->getMessage(), | |
$exception->getFile(), | |
$exception->getLine(), | |
$exception->getTrace() | |
); | |
} | |
/** | |
* エラーメッセージを整形して返します。 | |
* | |
* @param int エラーレベル | |
* @param string エラーメッセージ | |
* @param string エラーが発生したファイル | |
* @param int エラーが発生した行番号 | |
* @return string 整形したエラーメッセージ | |
*/ | |
public function errorMessage($errno, $errstr, $errfile, $errline, $trace) | |
{ | |
return $this->buildMessage( | |
$this->errorHeader($errno), | |
$errstr, | |
$errfile, | |
$errline, | |
$trace | |
); | |
} | |
/** | |
* スタックトレースを整形して返します。 | |
* | |
* @param array スタックトレース | |
* @return array 整形したスタックトレース | |
*/ | |
public function formatTrace(array $trace) | |
{ | |
$trace_formatted = array(); | |
foreach ($trace as $i => $t) { | |
$args = ''; | |
if (isset($t['args']) && !empty($t['args'])) { | |
$var_formatter = function($var) { | |
if (is_null($var)) { | |
return 'NULL'; | |
} | |
if (is_int($var)) { | |
return sprintf('Int(%d)', $var); | |
} | |
if (is_float($var)) { | |
return sprintf('Float(%F)', $var); | |
} | |
if (is_string($var)) { | |
return sprintf('"%s"', $var); | |
} | |
if (is_bool($var)) { | |
return sprintf('Bool(%s)', $var ? 'true' : 'false'); | |
} | |
if (is_array($var)) { | |
return 'Array'; | |
} | |
if (is_object($var)) { | |
return sprintf('Object(%s)', get_class($var), $var); | |
} | |
return sprintf('%s', gettype($var)); | |
}; | |
$args = implode(', ', array_map(function($arg) use ($var_formatter) { | |
if (is_array($arg)) { | |
$vars = array(); | |
foreach ($arg as $key => $var) { | |
$vars[] = sprintf('%s=>%s', | |
$var_formatter($key), $var_formatter($var)); | |
} | |
return sprintf('Array[%s]', implode(', ', $vars)); | |
} | |
return $var_formatter($arg); | |
}, $t['args'])); | |
} | |
$trace_formatted[] = sprintf('#%d %s(%d): %s%s%s(%s)', | |
$i, | |
(isset($t['file' ])) ? $t['file' ] : '', | |
(isset($t['line' ])) ? $t['line' ] : '', | |
(isset($t['class' ])) ? $t['class' ] : '', | |
(isset($t['type' ])) ? $t['type' ] : '', | |
(isset($t['function'])) ? $t['function'] : '', | |
$args); | |
} | |
return $trace_formatted; | |
} | |
/** | |
* ステータスコードからテキストフレーズを返します。 | |
* | |
* @return string ステータスのテキストフレーズ | |
*/ | |
public function statusText() | |
{ | |
if (!isset($this->status) || !isset(self::$statuses[$this->status])) { | |
return self::$statuses[500]; | |
} | |
return self::$statuses[$this->status]; | |
} | |
/** | |
* ステータスコードからレスポンスのステータスラインを返します。 | |
* | |
* @return string ステータスライン | |
*/ | |
public function statusLine() | |
{ | |
return sprintf('HTTP/1.1 %d %s', $this->status, $this->statusText()); | |
} | |
/** | |
* エラーメッセージをログに保存します。 | |
* | |
* @param string エラーメッセージ | |
*/ | |
public function log($message) | |
{ | |
if (isset($this->logger)) { | |
$logger = $this->logger; | |
$logger($message); | |
} | |
} | |
/** | |
* エラー画面の内容を返します。 | |
* | |
* @return string エラー画面 | |
*/ | |
public function content() | |
{ | |
$message = (!empty($this->messages)) ? implode("\n", $this->messages) : ''; | |
$status = $this->status; | |
$message = nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8')); | |
$title = htmlspecialchars($this->statusText(), ENT_QUOTES, 'UTF-8'); | |
$content = <<< CONTENT | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> | |
<meta name="robots" content="noindex,nofollow" /> | |
<title>{$title}</title> | |
</head> | |
<body> | |
<h1>{$title}</h1> | |
<p>{$message}</p> | |
</body> | |
</html> | |
CONTENT; | |
return $content; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// display_errorsがOFFの場合はステータスコードのみ返される | |
//ini_set('display_errors', 0); | |
// 標準エラーハンドラの出力をタグで囲む | |
ini_set('error_prepend_string', '<marquee class="php-error">'); | |
ini_set('error_append_string' , '</marquee>'); | |
error_reporting(E_ALL &~E_USER_NOTICE); | |
if (isset($_POST['exception']) && isset($_POST['status']) && ctype_digit($_POST['status'])) { | |
throw new \Acme\HttpException('Test', intval($_POST['status'])); | |
} elseif (isset($_POST['error'])) { | |
trigger_error('Test', E_USER_ERROR); | |
} elseif (isset($_POST['warning'])) { | |
trigger_error('Test', E_USER_WARNING); | |
} elseif (isset($_POST['notice'])) { | |
trigger_error('Test', E_USER_NOTICE); | |
} elseif (isset($_POST['fatal'])) { | |
NotDefinedClass::notDefinedMethod(); | |
} | |
?> | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="robots" content="noindex,nofollow" /> | |
<title>auto_prepend_fileでエラー制御</title> | |
</head> | |
<body> | |
<form action="<?=htmlspecialchars($_SERVER['SCRIPT_NAME'], ENT_QUOTES, 'UTF-8')?>" method="post"> | |
<label><input type="radio" name="status" value="400" />400</label> | |
<label><input type="radio" name="status" value="403" />403</label> | |
<label><input type="radio" name="status" value="404" />404</label> | |
<label><input type="radio" name="status" value="500" />500</label> | |
<input type="submit" name="exception" value="Exception" /> | |
<input type="submit" name="error" value="User Error" /> | |
<input type="submit" name="warning" value="User Warning" /> | |
<input type="submit" name="notice" value="User Notice" /> | |
<input type="submit" name="fatal" value="PHP Fatal Error" /> | |
</form> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment