在PHP中针对错误的配置有如下
1
2
3
4
log_errors
display_errors
error_log
error_reporting
error_reporting设置了报告的类型,log_errors决定了是否记录错误,记录的地方由error_log指定,display_errors决定是否显示错误信息。
如果你曾经使用过PHP的try语句,或者你会有疑问,错误和异常有何不一样?
简单来说,try块中的代码如果有抛出异常,可以被catch截获。但是发生错误还是会发送给PHP的错误处理程序。比如如下这段程序:
1
2
3
4
5
6
7
8
9
10
11
12
function doSomeThing($var){
throw new Exception("Please stop hitting me");
}
try{
$f = file("/www/test.txt");
$a = 10/0;
doSomeThing($a);
}catch(Exception $e){
echo "----->Exception";
}
这里面同时发生了PHP错误和抛出异常。PHP错误可能会记录到错误日志,而异常被catch捕获进行处理。(注意,如果发生比较严重的错误,比如语法错误,PHP直接中断解析,后面的方法不会被执行,也就不会有异常抛出了),PHP错误的处理默认是有PHP本身的默认程序处理的,不过可以使用set_error_handler方法来设置一个自定义的错误处理方法。
关于PHP的错误处理,可以参考:
http://cn2.php.net/manual/zh/errorfunc.configuration.php
http://cn2.php.net/manual/zh/errorfunc.constants.php
http://cn2.php.net/manual/zh/function.set-error-handler.php
由于我们将使用set_error_handler函数设置一个自定义错误处理程序来替代PHP标准的错误处理程序,所以对这个函数用法必须来个彻底认识。
1
mixed set_error_handler ( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] )
$error_handler设置回调函数,这个函数设置是有要求的,具体内容可参阅PHP文档,注意这个回调函数如果返回了false,则PHP的标准错误处理程序继续运行。我们看文档中的关于这个函数的描述:
“ 本函数可以用你自己定义的方式来处理运行中的错误, 例如,在应用程序中严重错误发生时,或者在特定条件下触发了一个错误(使用 trigger_error()),你需要对数据/文件做清理回收。
重要的是要记住 error_types 里指定的错误类型都会绕过 PHP 标准错误处理程序(默认是E_ALL | E_STRICT,就是全部错误类型都有自定义函数处理,否则没有包含的由PHP标准程序处理), 除非回调函数返回了 FALSE。 error_reporting() 设置将不会起到作用而你的错误处理函数继续会被调用 —— 不过你仍然可以获取 error_reporting 的当前值,并做适当处理(这里说的是error_reporting设置是针对标准错误处理程序的,它对自定义的错误处理程序无效)。 需要特别注意的是带 @ error-control operator 前缀的语句发生错误时,这个值会是 0(PHP中可以使用@前缀来强制一个语句不报告错误,实际的实现是把error_reporting设置为0,那么标准错误处理程序就不会处理错误,但是这个情况在使用了自定义错误处理程序时无效,有些错误自定义处理程序是无法处理的,实际上有些错误先于自定义错误处理程序前触发,它实际还是使用标准处理程序,所以@字符仍使用意义)。
同时注意,在需要时你有责任使用 die()。 如果错误处理程序返回了,脚本将会继续执行发生错误的后一行(注意这个,错误处理程序返回后脚本继续执行)。
以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在 调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT(这些级别不能使用自定义函数处理,实际上这些错误根本无法进入到自定义的错误处理程序,这些错误在PHP内核或编译时就被捕获,至于这些错误的处理则是由php.ini文件中的配置决定的,所以当在开发时,最好在php.ini中把display_errors调整为1,error_reporting调整为E_ALL | E_STRICT)。
如果错误发生在脚本执行之前(比如文件上传时),将不会 调用自定义的错误处理程序因为它尚未在那时注册。 ”
从上面的描述来看,我们总结一下:
比较严重的错误,自定义程序无法处理(实际是没有进入这个程序),自定义处理程序只能处理一般错误。一旦使用自定义处理程序来处理错误,那么error_reporting的设置对它是没有作用的(它只处理指定错误处理程序时给定的错误级别)。在开发时,为了能够显示和记录自定义程序无法处理的错误(比如严重错误),应该在php.ini中配置:
1
2
3
display_errors On
error_log /path/to/log/log.txt
error_reporting E_ALL | E_STRICT
注意error_reporting为E_ALL是表示所有错误,但是在PHP5.3中不包含E_STRICT,从PHP5.4开始才包含。display_errors表示从标准错误输出中输出错误,error_log指出记录错误日志的路径。
事实上,我们可以在脚本中改变这些值配置值(而不使用自定义错误处理函数),比如修改error_log,让它把错误记录到我们的指定的日志文件中。比如:http://blog.ifeeline.com/105.html中描述的就是这个情况,但是它只能记录一般错误,对于严重错误,还是根据php.ini配置文件的的设置去处理的。
相对这种记录错误的方法,对应还有一种使用自定义函数的实现方法。这个也是Magento中使用的方法。Magento中有一个叫开发者模式的设置。先记着这个。我们先看看错误处理程序设置的进入点,在index.php文件中有:
1
2
3
error_reporting(E_ALL | E_STRICT);
//接下来有一行注释的代码
#ini_set('display_errors', 1);
首先修改了错误报告的类型,我们知道,如果使用自定义错误处理程序,根本不受它的影响(指error_reporting设置的错误级别)。接下来的代码是设置是否显示错误,这应该是在没有使用自定义错误处理程序时配合使用的,要不然这两句代码大可以清理的了。
我们的应用从App的run方法或init方法开始,run方法调用baseInit,baseInit调用_initEnvironment,_initEnvironment内又调用setErrorHandler(self::DEFAULT_ERROR_HANDLER)方法:
1
2
3
4
5
6
//setErrorHandler(self::DEFAULT_ERROR_HANDLER) DEFAULT_ERROR_HANDLER->mageCoreErrorHandler
public function setErrorHandler($handler)
{
set_error_handler($handler);
return $this;
}
可见,直接使用set_error_handler函数直接设置错误处理程序为mageCoreErrorHandler,这个函数在app/code/core/Mage/Core/functions.php中定义。
1
2
3
4
5
6
7
8
9
function mageCoreErrorHandler($errno, $errstr, $errfile, $errline){
//...
$errorMessage .= ": {$errstr} in {$errfile} on line {$errline}";
if (Mage::getIsDeveloperMode()) {
throw new Exception($errorMessage);
} else {
Mage::log($errorMessage, Zend_Log::ERR);
}
}
这里省了一段代码,它罗列的所有错误代码,然后组建一个字符串$errorMessage,然后根据是否是开发模式,如果是就直接抛出异常,否则就把它记录到日志中。如果是记录到日志中,程序继续运行,现在问题是,如果抛出异常,谁来捕获这个异常,如何处理,程序是否继续运行?
这个需要回到App的run方法的包装函数Mage::run()函数中回答这个问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static function run($code = '', $type = 'store', $options = array())
{
try {
// .....
self::$_app->run(array(
'scope_code' => $code,
'scope_type' => $type,
'options' => $options,
));
} catch (Mage_Core_Model_Session_Exception $e) {
header('Location: ' . self::getBaseUrl());
die();
} catch (Mage_Core_Model_Store_Exception $e) {
require_once(self::getBaseDir() . DS . 'errors' . DS . '404.php');
die();
} catch (Exception $e) {
if (self::isInstalled() || self::$_isDownloader) {
self::printException($e);
exit();
}
try {
self::dispatchEvent('mage_run_exception', array('exception' => $e));
if (!headers_sent()) {
header('Location:' . self::getUrl('install'));
} else {
self::printException($e);
}
} catch (Exception $ne) {
self::printException($ne, $e->getMessage());
}
}
}
很明显,如果自定义错误处理程序抛出异常,就在这里被捕获处理,有些异常是给出链接,有些则是打印输出。这意味,要是打开开发者模式,可以直接查看到错误的输出。
在index.php中有如下代码:
1
2
3
if (isset($_SERVER['MAGE_IS_DEVELOPER_MODE'])) {
Mage::setIsDeveloperMode(true);
}
两个办法让这个代码生效,把条件去掉,或者在配置中设置MAGE_IS_DEVELOPER_MODE这个变量(一般直接去掉条件快速有实在)。
另外,在自定义的错误处理程序中,如果不是开发模式则不会抛出异常,错误就被记录到日志中,这个路径可以在后台配置:
System->Configuration->Developer->Log Settings
在调用Mage::log()函数时记录到System Log File Name,调用Mage::logException时记录到Exceptions Log File Name,实际上logException是log()函数的包装器,只是指定了不同的名字。
到此已经讨论了大部分内容了,我们需要谨记,仅仅依靠Magento中的自定义错误处理程序还是不够的,在开发时务必在PHP中做配置以快速找到错误。而在Magento开启开发者模式则可以直接输出异常信息,另外,可以有效利用Mage:log()来调试程序,它不受配置的影响,可以让它输出到自己期望的地方去。
继续查看在事件触发回调函数调用时的代码:
1
2
3
4
5
6
7
8
9
10
#File: app/code/core/Mage/Core/Model/App.php
protected function _callObserverMethod($object, $method, $observer)
{
if (method_exists($object, $method)) {
$object->$method($observer);
} elseif (Mage::getIsDeveloperMode()) {
Mage::throwException('Method "'.$method.'" is not defined in "'.get_class($object).'"');
}
return $this;
}
当回调函数不存在时,如果在开发模式下,就会抛出异常,这个可以让我们知道哪些回调函数不存在。
Magento中通过使用自定义的错误处理出现,把PHP的错误变成抛出异常。
(责任编辑:最模板)