PHP5でSTRICT有効でPEARを使う(NOTICE、STRICTをあやつる)
NOTICE、STRICTは怪しいコードを指摘してくれる、優れたエラーモードで次のように書くことで有効になります。
error_reporting(E_ALL|E_STRICT);
ところが、PEARの多くのモジュールはPHP4でも動作するよう作られているため、STRICTを有効にすると、大量のSTRICTが発生します。
そこで・・・
次のことを実現するクラスを作りました。
さらに、Exception、PEAR_Exceptinon もまとめて扱うことで、PHPのエラーに関する出力を統合してみました。
PEARと書いてきましたが、NOTICE、STRICTが発生するライブラリ全般に使えます。また、今回作ったのはPHP5用ですが Exception まわりの処理を削除すれば、PHP4で NOTICE を操ることはできます。
素のPHPと、ErrorHandlerクラス使用の例
次のスクリプトを使いました。
<?php error_reporting(E_ALL|E_STRICT); mb_internal_encoding('UTF-8'); mb_regex_encoding('UTF-8'); date_default_timezone_set('Asia/Tokyo'); ini_set('display_errors', 1); ini_set('default_charset', 'UTF-8'); echo 'phpversion : ' . phpversion() . '<br />'; //require_once('ErrorHandler.php'); //$error_handle = ErrorHandler::singleton(); //$error_handle->addIgnoreError(array('errfile' => '#/PEAR/PEAR.php$#i', 'errno' => E_STRICT)); require_once('PEAR.php'); // requireしただけでSTRICTになる。 echo $xxx; // 未定義変数なのでNOTICEになる。 trigger_error('ユーザエラーを投げる', E_USER_ERROR); ?>
次が素のPHPで実行したとき。(PEAR.phpでSTRICTが発生してるのを確認してください)
phpversion : 5.2.2 Strict Standards: Assigning the return value of new by reference is deprecated in C:\xampp\php\PEAR\PEAR.php on line 563 Strict Standards: Assigning the return value of new by reference is deprecated in C:\xampp\php\PEAR\PEAR.php on line 566 Notice: Undefined variable: xxx in C:\xampp\htdocs\errorhandler\test1.php on line 15 Fatal error: ユーザエラーを投げる in C:\xampp\htdocs\errorhandler\test1.php on line 16
次に、先のスクリプトのコメント3行を生かして、ErrorHandlerクラスを使うと、先ほどのSTRICTが無くなってるのが分かります。
phpversion : 5.2.2 E_NOTICE(8) Undefined variable: xxx C:\xampp\htdocs\errorhandler\test1.php (15) #0 C:\xampp\htdocs\errorhandler\test1.php(15): ErrorHandler->error_handler(8, 'Undefined varia...', 'C:\xampp\htdocs...', 15, Array) #1 {main} date : 2007-11-11 11:11:11 ---------------------------------------------------------------- E_USER_ERROR(256) ユーザエラーを投げる C:\xampp\htdocs\errorhandler\test1.php (16) #0 [internal function]: ErrorHandler->error_handler(256, '???????????????...', 'C:\xampp\htdocs...', 16, Array) #1 C:\xampp\htdocs\errorhandler\test1.php(16): trigger_error('???????????????...', 256) #2 {main} -- $_GET ------------------------------------------------------- Array ( :
ErrorHandlerクラスの使い方
まず基本はこれだけです。
ini_set('display_errors', 1); error_reporting(E_ALL|E_STRICT); require_once('ErrorHandler.php'); $error_handle = ErrorHandler::singleton();
次に、STRICTなどを個別に操る方法です。これで「/PEAR/PEAR.php で発生する STRICT を無視」を設定できます。errfileへは正規表現で条件を記述します。
$error_handle->addIgnoreError(array('errfile' => '#/PEAR/PEAR.php$#i', 'errno' => E_STRICT));
次の例は「STRICT かつ errstr が Non-static method PEAR::isError()... を無視」です。errstrが'='で始まっていますが、これは完全一致を意味します。正規表現でも記述できます。
$error_handle->addIgnoreError(array('errno' => E_STRICT, 'errstr' => '=Non-static method PEAR::isError() should not be called statically, assuming $this from incompatible context'));
実はPEARの場合、手っ取り早いのは次の設定です。
$error_handle->addIgnoreError(array('errfile' => '#/PEAR/#i')); $error_handle->addIgnoreError(array('errstr' => '#/PEAR/#i'));
しかしながら、自分で書いたスクリプト上で次のようなSTRICTが発生することもあるので、そのような場合は、順次設定を追加する必要があります。
'Non-static method Mail::factory() should not be called statically, assuming $this from incompatible context'
ErrorHandlerクラスの解説
詳しくはソース参照
- set_error_handler()、set_exception_handler() でエラー・未catchの例外を受けるハンドラをセットしています。
- エラー・未catch例外発生時は、display_errors、error_reporting() から判断して表示します。
- ただし、無視するエラーとして登録されていれば無視します。
ErrorHandlerクラスソース
ErrorHandler.php
<?php require_once('PEAR/Exception.php'); class ErrorHandler { /** * 無視するエラー * * @var array */ protected $_ignore_error = array(); /** * シングルトン * * @param array $opt_ オプション * @return Weed_ErrorHandler */ static public function singleton() { static $singleton = null; if ($singleton === null) { $class_name = get_class(); $singleton = new $class_name(); // エラーハンドラ設定 set_error_handler(array($singleton, 'error_handler')); // キャッチされなかった例外のハンドラ設定 set_exception_handler(array($singleton, 'uncatched_exception_handler')); } return $singleton; } /** * 無視するエラーを登録 * * 'errno' 'errstr' 'errfile' 'errline' をキーとして条件記述する。 * * 'errstr' 'errfile' は正規表現で指定する。 * array('errfile' => '#/PEAR/PEAR.php$#i', 'errno' => E_STRICT) * * 完全一致で指定するときは先頭文字を'='で始めてもよい。 * array('errno' => E_STRICT, 'errstr' => '=Non-static method PEAR::isError() should not be called statically, assuming $this from incompatible context') * * @param array $errinfo_ 条件 */ public function addIgnoreError($errinfo_ = array()) { $this->_ignore_error[] = $errinfo_; } /** * 無視してもよいエラーか * * @param array $errinfo_ * @return bool */ public function isIgnoreError($errinfo_ = array()) { if (isset($errinfo_['errfile'])) { // windows 対応、ファイルパスの区切りを変換 $errinfo_['errfile'] = strtoupper(strtr($errinfo_['errfile'], '\\', '/')); } foreach ($this->_ignore_error as $ignore) { $hit_flg = true; foreach (array_keys($ignore) as $key) { if (!isset($errinfo_[$key])) { $hit_flg = false; break; } if ($key === 'errstr' || $key === 'errfile') { if ($ignore[$key]{0} === '=') { if ($ignore[$key] !== ('=' . $errinfo_[$key])) { $hit_flg = false; break; } } else if (0 === preg_match($ignore[$key], $errinfo_[$key])) { $hit_flg = false; break; } } else if ($key === 'errno' || $key === 'errline') { if ($ignore[$key] !== $errinfo_[$key]) { $hit_flg = false; break; } } else { return false; } } if ($hit_flg === true) { return true; } } return false; } /** * 例外を文字列に変換 * * @param Exception $e_ * @return string */ public static function exceptionToString($e_) { $msg = ''; $exx = new PEAR_Exception('', $e_); $causes = $exx->getCause(); if ($causes != null) { if (!is_array($causes)) { $causes = array($causes); } foreach ($causes as $i => $cause) { /* @var $cause Exception */ if ($cause instanceof PEAR_Exception) { if ($cause->getCause() !== null) { $msg .= self::exceptionToString($cause->getCause()); } } else { if (! $cause instanceof Exception) { $msg .= str_repeat(' ', $i) . (string)$cause . PHP_EOL; continue; } } $msg .= str_repeat(' ', $i) . get_class($cause) . ': '; $msg .= $cause->getMessage() . ' in ' . $cause->getFile(); $msg .= ' on line ' . $cause->getLine() . PHP_EOL; $msg .= $cause->getTraceAsString() . PHP_EOL; // $msg .= print_r($cause->getTrace(), 1) . PHP_EOL; } } return $msg; } /** * エラーハンドラ定義 * * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline */ public function error_handler($errno, $errstr, $errfile, $errline) { // @ でエラー出力を抑制していても、エラーハンドラがCallされる。 // ただし、@ の場合は一時的に error_reporting() == 0 となるので // これをチェックすることで、@ の有り無しを判別する。 if (error_reporting() === 0) { return; } // error_reporting にセットされているエラーレベルではない場合、 // 除外する。 if ((error_reporting() & $errno) === 0) { return; } // 無視するエラーかチェック $errinfo = array( 'errno' => $errno, 'errstr' => $errstr, 'errfile' => $errfile, 'errline' => $errline, ); if ($this->isIgnoreError($errinfo) === true) { return; } // エラー番号とエラーレベルのマッピング $errlev = array( E_USER_ERROR => 'E_USER_ERROR', E_ERROR => 'E_ERROR', E_USER_WARNING => 'E_USER_WARNING', E_WARNING => 'E_WARNING', E_USER_NOTICE => 'E_USER_NOTICE', E_NOTICE => 'E_NOTICE', E_STRICT => 'E_STRICT' ); $add_msg = 'errno'; if (isset($errlev[$errno])) { $add_msg = $errlev[$errno]; } $add_msg .= '(' . $errno . ')' . PHP_EOL; $add_msg .= $errstr . PHP_EOL; $add_msg .= $errfile . ' (' . $errline . ') ' . PHP_EOL; $exx = null; try { throw new Exception(); } catch (Exception $e) { $exx = $e; } $trace = self::exceptionToString($exx); $arr = preg_split('/[\r\n]+/', $trace); array_shift($arr); $trace = join(PHP_EOL, $arr); $add_msg .= $trace . PHP_EOL; $exit_flg = true; if ($errlev[$errno] === 'E_NOTICE' || $errlev[$errno] === 'E_WARNING' || $errlev[$errno] === 'E_STRICT') { $exit_flg = false; } self::_error_output($add_msg, $exit_flg); } /** * キャッチされなかった例外用のハンドラ定義 * * @param Exception $exception */ public function uncatched_exception_handler($exception) { $add_msg= ''; $add_msg .= 'UNCATCHED EXCEPTION' . PHP_EOL; $add_msg .= self::exceptionToString($exception) . PHP_EOL; $exit_flg = true; self::_error_output($add_msg, $exit_flg); } /** * エラーを画面などに出力 * * @param string $add_msg_ * @param bool $exit_flg_ */ protected static function _error_output($add_msg_, $exit_flg_) { // メッセージ作成 if ($exit_flg_) { $msg = ''; $msg .= 'date : ' . date('Y-m-d H:i:s') . PHP_EOL; $msg .= '----------------------------------------------------------------' . PHP_EOL; $msg .= $add_msg_; if (isset($_GET)) { $msg .= '-- $_GET -------------------------------------------------------' . PHP_EOL; $msg .= print_r($_GET, true) . PHP_EOL; } if (isset($_POST)) { $msg .= '-- $_POST ------------------------------------------------------' . PHP_EOL; $msg .= print_r($_POST, true) . PHP_EOL; } if (isset($_FILES)) { $msg .= '-- $_FILES -----------------------------------------------------' . PHP_EOL; $msg .= print_r($_FILES, true) . PHP_EOL; } if (isset($_COOKIE)) { $msg .= '-- $_COOKIE ----------------------------------------------------' . PHP_EOL; $msg .= print_r($_COOKIE, true) . PHP_EOL; } if (isset($_SESSION)) { $msg .= '-- $_SESSION ---------------------------------------------------' . PHP_EOL; $msg .= print_r($_SESSION, true) . PHP_EOL; } if (isset($_SERVER)) { $msg .= '-- $_SERVER ----------------------------------------------------' . PHP_EOL; $msg .= print_r($_SERVER, true) . PHP_EOL; } } else { $msg = $add_msg_; } // 記録 // if ((int)ini_get('log_errors') !== 0) { // error_log($msg); // } // // ファイルへ出力 // list($usec, $sec) = explode(" ", microtime()); // $filename = D_DIR_ERRLOG . date('YmdHis') . '-' . $usec . '-' . $errlev[$errno] . '.log'; // $h = fopen($filename, "w"); // fputs($h, $msg); // fclose($h); // 画面へ表示 if ((int)ini_get('display_errors') !== 0) { echo '<pre>' . htmlentities($msg, ENT_QUOTES, mb_internal_encoding()) . '</pre>'; } if ($exit_flg_) { exit; } } } ?>