Skip to content

Instantly share code, notes, and snippets.

@k-holy
Created September 25, 2012 11:09
Show Gist options
  • Save k-holy/3781180 to your computer and use it in GitHub Desktop.
Save k-holy/3781180 to your computer and use it in GitHub Desktop.
シャットダウン関数でエラー処理
php_value auto_prepend_file __prepend.php
<?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;
}
});
<?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;
}
}
<?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