キャッシュありでテンプレートで発生したFatalが表示されないバグ
どんな問題か?
setup.phpから以下設定しておく。
- キャッシュあり。
- 更新周期0s
test.php
<?php include_once dirname(__FILE__) . '/__init__.php'; Rhaco::import('tag.HtmlParser'); $p = new HtmlParser('test.html'); $p->write(); ?>
test.html
<html> <head> </head> <body> <?php echo phpversion() . "<br/>\n"; // Strict Standards //function & aaa(){ //$ret = & new stdClass(); //return $ret; //} //$ccc = & aaa(); //echo $xxx; // Notice: {}} // Parse error: //xxx(); // Fatal error: ?> </body> </html>
test.php を実行するとテンプレートで以下となる。
- Parser errorが発生しても画面に表示されない。
- Noticeが発生したときのログが成形されていない。(これはついでに修正してみました)
画面イメージは次の通り。
修正してみた。
ob_start、register_shutdown_function あたり挙動がPHP4、PHP5で異なる。 - gounx2の日記 を踏まえて修正してみた。
ポイントは。
エラーの検出は2つ考慮が必要
- Noticeなどerror_handlerで取得できるエラー
- Parser errorなどerror_handlerで取得できないエラー
あと、ob_get_clean の実行タイミング。
rhaco/io/Snapshot.php(rev3175)
<?php Rhaco::import("resources.Message"); Rhaco::import("io.FileUtil"); Rhaco::import("util.Logger"); Rhaco::import("lang.Variable"); Rhaco::import("lang.ArrayUtil"); Rhaco::import("lang.Env"); Rhaco::import("tag.model.TemplateFormatter"); /** * スナップショットを操作するクラス * @author Kazutaka Tokushima * @license New BSD License * @copyright Copyright 2005- rhaco project. All rights reserved. */ class Snapshot{ var $start = false; var $url = ""; var $variables = array(); var $buffer = ""; var $id = 0; /** * スナップショットの取得を開始する * * @param string $url * @param array $variables * @return Snapshot */ function Snapshot($url="",$variables=array()){ /*** * $snap = new Snapshot(); * print("A"); * eq("A",$snap->get()); * * $snap1 = new Snapshot(); * print("A"); * $snap2 = new Snapshot(); * print("a"); * $snap3 = new Snapshot(); * print("1"); * eq("1",$snap3->get()); * eq("a",$snap2->get()); * print("B"); * eq("AB",$snap1->get()); */ $id = sizeof(Rhaco::getVariable("RHACO_CORE_SNAPSHOT_COUNT")); Rhaco::addVariable("RHACO_CORE_SNAPSHOT_COUNT",$id,$id); $this->id = $id; $this->start = true; $this->url = $url; $this->variables = ArrayUtil::arrays($variables); Rhaco::register_shutdown(array($this,"close")); Logger::deep_debug(Message::_("start snapshot({1})",$this->id)); $GLOBALS['BufferedErrors']=Array(); set_error_handler(array($this, '_error_handler')); ob_start(); } // Snapshot中のエラーを捕捉するためのハンドラ function _error_handler($errno, $errstr, $errfile, $errline, $errcontext) { if(@Rhaco::error($errno, $errstr, $errfile, $errline, $errcontext)) return true; $errorTypes = Array( E_ERROR => 'Fatal error', // 実際には補足できない。 E_WARNING => 'Warning', E_PARSE => 'Parse error', // 実際には補足できない。 E_NOTICE => 'Notice', E_CORE_ERROR => 'Fatal Core Error', // 実際には補足できない。 E_CORE_WARNING => 'Core Warning', // 実際には捕捉できない。 E_COMPILE_ERROR => 'Compilation Error', // 実際には捕捉できない。 E_COMPILE_WARNING => 'Compilation Warning', // 実際には捕捉できない。 E_USER_ERROR => 'Fatal error', E_USER_WARNING => 'Warning', E_USER_NOTICE => 'Notice', 2048 => 'Strict Standards', 4096 => 'Catchable Fatal Error' // 実際には捕捉できない。 ); $errmsg = sprintf( "%s: %s in %s on line %d", $errorTypes[$errno], $errstr, $errfile, $errline); $GLOBALS['BufferedErrors'][]=array('type'=>'warning', 'msg'=>$errmsg); return false; } /** * スナップショットの取得を終了する */ function close(){ /*** unit("io.SnapshotTest"); */ if($this->start){ if($this->buffer === ""){ $flg = 1; // get()が呼ばれずclose()されたとき、スクリプトの実行が中断される重大なエラー発生と判断する。 $this->buffer = ob_get_contents(); // if(preg_match("/Fatal error.+on line.+/",$this->buffer,$match)){ // Logger::error(str_replace(array("<b>","</b>","<br />"),array("","",""),$match[0])); // } // error_handler で捕捉できないエラー(Fatal/Parse)は、ob_get_contentsから探して判断する。 // これができるのはPHP5だけ。PHP4は、ob_get_contentsが空文字列になってる。 $arr=array('Fatal error','Parse error'); foreach($arr as $key){ if(preg_match("/".$key.".+on line.+/",$this->buffer,$match)){ $errmsg = str_replace(array("<b>","</b>","<br />"),array("","",""),$match[0]); $GLOBALS['BufferedErrors'][]=array('type'=>'error', 'msg'=>$errmsg); } } } //PHP5のとき、shutdown関数内でcleanするとエラーが表示されないので、 //重大なエラーでスクリプト中断されたときは、cleanしないようにしてみた。 //「shutdownのときは」とかで見れたほうがより確実なのかも。 if(!isset($flg)){ ob_get_clean(); } restore_error_handler(); //捕捉したエラーをログ出力 foreach($GLOBALS['BufferedErrors'] as $err){ if($err['type'] === 'error') Logger::error($err['msg']); else Logger::warning($err['msg']); } $this->start = false; Rhaco::clearVariable("RHACO_CORE_SNAPSHOT_COUNT",$this->id); Logger::deep_debug(Message::_("end snapshot({1})",$this->id)); } }
rhaco/tag/TagParser.php(rev3237)
Snapshot側でエラーのログを全て行うので、ここでのNoticeをログする処理は削除した。
/** * テンプレートをフォーマットし取得する * @param string $templateFileName テンプレートファイルパス(resources/templates)からの相対 * @param string $remotePath 相対パス変換用のルートパス * @param array $variables テンプレートで利用する変数(hash) * @return string */ function read($filename="",$variables=array(),$remotePath=""){ /*** unit("tag.TagParserTest"); */ $this->filename = empty($filename) ? $this->filename : $filename; if(empty($this->filename)) return ExceptionTrigger::raise(new NotFoundException("template")); $filename = Url::parseAbsolute($this->path,$this->filename); $variables = $this->_setSpecialVariables(array_merge(ArrayUtil::arrays($variables),ArrayUtil::arrays($this->variables))); $cacheurl = $this->_getCacheUrl($filename); $this->tmpurl = empty($remotePath) ? $this->url : $remotePath; $rhaco_tag_parse_src = null; if(!Variable::bool(Rhaco::constant("NOT_MAKE_CACHE")) && Variable::bool(Rhaco::constant("TEMPLATE_CACHE")) && !Cache::isExpiry($cacheurl,Rhaco::constant("TEMPLATE_CACHE_TIME",86400)) && (FileUtil::time($filename) < Cache::time($cacheurl)) ){ $rhaco_tag_parse_src = Cache::execute($cacheurl,$variables); }else{ $rhaco_tag_parser_read_src = $this->_parse($this->_getTemplateSource($filename)); if(Variable::bool(Rhaco::constant("TEMPLATE_CACHE")) && !Variable::bool(Rhaco::constant("NOT_MAKE_CACHE"))){ Cache::set($cacheurl,$rhaco_tag_parser_read_src); $rhaco_tag_parse_src = Cache::execute($cacheurl,$variables); } if($rhaco_tag_parse_src === null){ $rhaco_snapshot = new Snapshot(); Rhaco::execute($rhaco_tag_parser_read_src,$variables); $rhaco_tag_parse_src = $rhaco_snapshot->get(); } unset($rhaco_snapshot,$rhaco_tag_parser_read_src); } unset($filename,$variables,$cacheurl); $src = StringUtil::encode($this->_callFilter("publish",$this->_call($rhaco_tag_parse_src,"_doRead")),$this->encodeType); // if(preg_match_all("/Notice.+/",$src,$match)) Logger::warning($match); return $src; }
修正後の画面イメージは次の通り。