这话题已经没有什么新意了,这里只是做做笔记,作为思路的一种整理,也以便后续忘了可以回来这里查找。
错误
以下是 PHP 最常见的几种错误:
// E_NOTICE
echo $a;
// E_WARNING
echo 100 / 0;
class Sample
{
public function method()
{
//not static method
}
}
// E_STRICT
Sample::method();
// E_ERROR
new Dummy();
运行上面代码,页面输出以下信息:
Notice: Undefined variable: a in D:\errors-exceptions\demo4.php on line 6
Warning: Division by zero in D:\errors-exceptions\demo4.php on line 9
Strict Standards: Non-static method Sample::method() should not be called statically in D:\errors-exceptions\demo4.php on line 20
Fatal error: Class 'Dummy' not found in D:\errors-exceptions\demo4.php on line 23
在生产环境下,是不允许把错误信息输出到页面的。
怎么办?关闭错误输出
ini_set('display_errors', 0);
此时,刷新页面,页面将不会报任何错误。页面一片空白,或者显示 500 错误。
这也不是我们希望的,虽然不把错误输出到页面,但是这些错误我们是希望把它们都收集起来,写到日志里面,以便开发人员能够不断改进代码,排查错误。
怎么办?自定义错误处理
set_error_handler(function($errno, $errstr, $errfile, $errline) {
//在这里对错误进行处理
echo $errstr . '<br>';
});
运行上面的代码,页面输出:
Undefined variable: a
Division by zero
Non-static method Sample::method() should not be called statically
很奇怪,不是应该输出 4 个错误吗?怎么 Fatal error
没有捕捉到。查看 set_error_handler 帮助文档,我们发现,它有以下描述:
以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在 调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT。
噢~,原来是这样,原来 set_error_handler
方法是不能捕捉 E_ERROR
错误的(上面的 Fatal error)。那么这类错误,我们是不能放过的,也必须捕捉到,并做适当的处理。
怎么办? 利用 register_shutdown_function 函数
register_shutdown_function(function() {
if(is_null($e = error_get_last()) === false) {
echo $e['message'] . '<br>';
}
});
此时,再运行上面的例子,页面会输出:
Undefined variable: a
Division by zero
Non-static method Sample::method() should not be called statically
Class 'Dummy' not found
很好,四种错误,我们都成功捕捉了,并按照我们自己的方式进行了输出。
到目前为止,应该说能捕捉的 PHP 错误,我们都捕捉到了,当然还有一些错误,压根就没有办法捕捉。比如语法错误,这类错误是在 PHP 引擎对即将执行的文件编译期间的错误。
<?php
sfdsdfdsfsfsdf
//页面输出
Parse error: syntax error, unexpected end of file
像上面这种错误,程序就没有办法捕捉了。只要程序是经过测试的,一般不会在生产环境出现此类错误,所以也不用过于但心。
最后注:register_shutdown_function
函数会在任何导致页面退出的时候,会被调用。比如发生了致命错误、使用 exit() 函数、又或者是页面执行完毕了,都会触发该函数。利用这个特征,我们可以在页面退出时,获取到最后一个错误,然后进行记录。这个的 “最后一个错误” 往往是致命错误,原因很简单:因为一旦错误被 set_error_handler
捕捉到了,那么 register_shutdown_function
将捕捉不了。而 前者捕捉不了的,才会被后者捕捉。
异常
在 PHP 中,所以异常的基类都是 Exception。异常应该说是在 PHP 后来引入了面向对象的概念后,才有的产物。那么说,PHP 原来只抛错误,却没有异常的概念了。但现在,异常的使用已经非常广泛了,我们有必要学习一下。
好,我们来制造一些异常:
new PDO('mysql:dbname=testdb;host=127.0.0.1', 'root', 'wrong_passwd');
//运行结果
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)' in D:\errors-exceptions\demo6.php:6 Stack trace: #0 D:\errors-exceptions\demo6.php(6): PDO->__construct('mysql:dbname=te...', 'root', 'wrong_passwd') #1 {main} thrown in D:\errors-exceptions\demo6.php on line 6
再制造一个:
throw new Exception('我是异常');
//运行结果
Fatal error: Uncaught exception 'Exception' with message '我是异常' in D:\errors-exceptions\demo6.php:9 Stack trace: #0 {main} thrown in D:\errors-exceptions\demo6.php on line 9
在生产环境下,页面直接输出这些异常,同样是不优雅的,那么我们同样可以像关闭错误输出一样,关闭异常的输出:
ini_set('display_errors', 0);
同样地,异常虽然不显示出来了,但是我们需要记录并处理这些异常。
怎样做? 使用 set_exception_handler 函数
set_exception_handler(function($exception) {
//在这里,统一处理异常
echo get_class($exception) .': '. $exception->getMessage();
});
此时,再运行页面,会输出以下信息:
PDOException: SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)
小结
在我刚开始学习 PHP 的时候,的确被它的错误和异常困扰了许久。最开始,我甚至不会用异常。实际上,在现代的 PHP 里面,我们基本上可以完全控制它的异常和错误。只需要分开处理就可以了。
扩展:我们在实际项目中捕捉并处理异常和错误的时候,往往是把两者合二为一。就是说把异常也当成错误来处理。又或者反过来,把错误当成异常来处理。
下面,就让我们一起看看,如何把两者相互转换:
把异常转换为错误处理
<?php
register_shutdown_function('shutdownHandler');
set_error_handler('errorHandler');
set_exception_handler('exceptionHandler');
//自定义错误处理
function errorHandler($code, $message, $file, $line) {
//在这里,对错误进行后续处理。比如:写日志、发邮件等等
echo ' Code: '.$code
.', Message: '.$message
.', File: ' . $file
.', Line: ' . $line
. '<br>';
}
//自定义异常处理
function exceptionHandler($exception) {
errorHandler(
$exception->getCode(),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine()
);
}
//自定义致命错误处理
function shutdownHandler() {
if(is_null($e = error_get_last()) === false) {
errorHandler(
$e['type'],
$e['message'],
$e['file'],
$e['line']
);
}
}
不过这样处理法,会导致异常的堆栈信息“丢失”,意思是无法处理这些堆栈信息了。当然是有办法处理的了,具体请看 demo3.php。
再看看,如何把错误转换为异常 (注意:以下代码仅为了演示其原理,代码本身并非完全合理):
set_error_handler('errorHandler');
function errorHandler($errno, $errstr, $errfile, $errline) {
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
};
大家看到,我们使用了 ErrorException 这个类,这是 PHP 后来才引入的,叫作错误异常。当然,我猜它的目的,应该也是为了能实现错误与异常之间优雅转换而添加的。
至此,错误与异常的学习基本完毕。最后推荐看看一个网站有关于对 PHP 错误与异常的介绍,尤其是对异常的一些行为的说明,都是值得注意的,网站在这里:PHP - Error & Exception Handling
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。