错误与异常

错误异常 在PHP 中不一样的, 它们都表明代码出现问题, 且都能提供错误信息.

Points:

  • 错误 出现的时机比异常早
  • 错误 可以委托给全局错误处理器处理, 有些错误是无法恢复的, 会导致脚本停止
  • 异常 要先实例化(Exception类), 然后抛出, 可以被捕获(try...catch)
  • 异常 捕获后可以就地处理, 无需停止脚本(任何未被捕获的异常都会导致脚本停止)

异常的使用:

  • 主动出击: 在遇到无法修复的状况时(当前上下文不知道如何处理)主动抛出, 交由使用者处理

    eg. 数据库连接超时, 传入参数类型不符合条件等.

    eg. 组件和框架的作者尤其无法确定如何处理异常状况, 通常会抛出异常, 交由具体使用者去处理.

  • 被动防守: 预测潜在的问题, 减轻影响(将可能抛出异常的代码放在 try/catch 块中)

PHP 7 注意:

PHP 7中, 大多数错误被作为 Error异常 抛出, 能够被捕获.

若未被捕获且未注册异常处理函数(通过 set_exception_handler() 注册), 则会按照传统方式处理(指PHP7之前版本): 被报告为一个致命错误(Fatal Error), 可被 set_error_handler() 处理.

异常类

PHP 内置异常类:

SPL 扩充的异常类(均继承自 Exception 类):

错误类(PHP >= 7)

  • Throwable

    • Error

      • ArithmeticError

        • DivisionByZeroError
      • AssertionError
      • ParseError
      • TypeError
    • Exception
      • ...

注意: PHP 7 中, Error 和 Exception 都继承自 Throwable, 因此在捕获(try...catch)时可通过捕获 Throwable 来同时捕获异常和错误

try {
// do something
} catch (\Throwable $e) {
// log error or sth.
}

错误

php 能触发不同类型的错误:

  • 致命错误
  • 运行时错误
  • 编译时错误
  • 启动错误
  • 用户触发错误(少见)

错误报告级别

error_reporting(int $level);

PHP 5.3 及以上, 默认的错误报告级别是 E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED (不会显示 E_NOTICEE_STRICTE_DEPRECATED)

PHP Manual 所有的错误级别

部分错误级别解释:

错误级别 解释 建议
E_NOTICE 运行时通知。表示脚本遇到可能会表现为错误的情况,但是在可以正常运行的脚本里面也可能会有类似的通知。 开发期间使用, 会对代码中可能出现的bug进行警告, 节省调试时间
E_STRICT 启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。
E_ALL 不包含 E_STRICT, 因此默认不激活
开发期间使用
E_DEPRECATED 运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。 开启

错误报告设置

错误报告遵循原则

  • 报告错误
  • 开发环境要显示错误
  • 生产环境要不能显示错误(安全考虑)
  • 开发环境和生产环境都要记录错误

注意分清 报告错误显示错误 这两个概念的区别.

php.ini

开发环境推荐错误报告方式

;显示错误
display_startup_errors = On
display_errors = On ;报告错误
error_reporting = -1 ;记录错误
log_errors = On

生产环境推荐错误报告方式

;显示错误
display_startup_errors = Off
display_errors = Off ;报告错误
error_reporting = E_ALL & ~E_NOTICE ;记录错误
log_errors = On

部分参数解释

参数 解释 建议
display_errors 设置是否将错误信息作为输出的一部分显示到屏幕,或者对用户隐藏而不显示 开发环境打开
生产环境务必关闭
display_startup_errors 即使 display_errors 设置为开启, PHP 启动过程中的错误信息也不会被显示。强烈建议除了调试目的以外,将 display_startup_errors 设置为关闭。 开发环境打开
生产环境务必关闭
log_errors 设置是否将脚本运行的错误信息记录到服务器错误日志或者error_log之中 打开

范例代码

<?php

// 关闭所有PHP错误报告
error_reporting(0); // Report simple running errors
error_reporting(E_ERROR | E_WARNING | E_PARSE); // 报告 E_NOTICE也挺好 (报告未初始化的变量
// 或者捕获变量名的错误拼写)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE); // 除了 E_NOTICE,报告其他所有错误
error_reporting(E_ALL ^ E_NOTICE); // 报告所有 PHP 错误 (参见 changelog)
error_reporting(E_ALL); // 报告所有 PHP 错误
error_reporting(-1); // 和 error_reporting(E_ALL); 一样
ini_set('error_reporting', E_ALL); ?>

全局异常处理程序

捕获所有未被捕获的异常: 通过 set_exception_handler 注册全局异常处理程序.

函数

set_exception_handler ( callable $exception_handler ) : callable
// 注册异常处理程序
set_exception_handler('handleException'); // 重置异常处理程序为默认值
// set_exception_handler(null); // 还原成之前的异常处理程序
// restore_exception_handler(); // < PHP 7
handleException(Exception $ex)
{
// 记录错误日志
echo "Uncaught exception: " , $ex->getMessage(), "\n";
// 开发环境显示调试信息(推荐 filp/whoops 扩展包)
// ...
// 生产环境显示对用户友好的页面(信息)
// ... } // >= PHP 7
// 大多数错误抛出 Error 异常, 也能被捕获, 因此参数类型必须是 Throwable, 否则会引起问题.
handleException(Throwable $ex)
{
// ...
}

在用户自定义异常处理函数内部, 可根据情况做一下处理:

  • 日志记录错误
  • web 渲染错误页面
  • console 渲染错误提示

全局错误处理函数

通过设置全局错误处理程序, 使用自己的自定义方式拦截并处理PHP错误, 包括但不限于:

  • 记录详细错误日志
  • 对数据/文件做清理回收
  • 转换成 ErrorException 对象, 再由处理异常的流程来处理错误.

注册全局错误处理程序

set_error_handler( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] ) : mixed

$error_types 指定的错误类型会被该错误处理函数拦截 ( 除非该函数返回了 false),不受 error_report() 影响.

处理程序

# 错误处理函数参数
# $errno 错误等级(对应 E_* 常量)
# $errstr 错误消息
# $errfile 发生错误的文件名
# $errline 发生错误的行号
# $errcontext 一个数组, 指向错误发生时可用的符号表(可选参数), 通常不用(php7.2后废弃)
function error_handler(int $errno, string $errstr, string $errfile, int $errline, array $errcontext) {
// 处理错误
}

带 @ 前缀的语句发生错误时, $errno 值为 0

  • 脚本会在错误处理函数结束后从出错的地方继续执行 (因此必要时需主动调用 die()exit() 以结束脚本)

  • 如果错误发生在脚本执行之前(比如文件上传时),将不会 调用自定义的错误处理程序因为它尚未在那时注册。

  • 如果函数返回 FALSE,标准错误处理处理程序将会继续调用。

无法捕获的错误类型

以下级别的错误不能由用户定义的错误处理函数来捕获:

错误级别 解释
E_ERROR 致命的运行错误, 一般不可恢复(eg. 内存分配导致的问题)
E_PARSE 编译时语法解析错误, 由分析器产生
E_CORE_ERROR PHP初始化启动过程中发生的致命错误, 由php引擎核心产生
E_CORE_WARNING PHP初始化启动过程中发生的警告(非致命错误), 由php引擎核心产生
E_COMPILE_ERROR 致命编译时错误。类似E_ERROR, 但是是由Zend脚本引擎产生的。
E_COMPILE_WARNING 编译时警告 (非致命错误)。类似 E_WARNING,但是是由Zend脚本引擎产生的。

以及在 调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT

这些无法捕获的错误, 可在 register_shutdown_function() 中处理( 但脚本仍会结束 )

范例代码

function handleError($errno, $errstr, $errfile = '', $errline = 0)
{
if (!(error_reporting() & $errno)) {
// 错误类型未包含在 error_reporting() 里, 因此将它交由PHP标准错误处理程序来处理
return false;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}

错误 转换为 异常

开发/生产环境处理错误和异常

开发环境

php 默认的错误信息很糟糕, 为了更高的帮助调试程序, 可以使用 filp/whoops 扩展包.

安装

composer require filp/whoops

使用(web)

$whoops = new \Whoops\Run;
$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
$whoops->register();

如果php脚本触发PHP错误, 或应用没有捕获异常, 则开发人员就可以看到 Whoops 的图形化诊断页面.

可用的处理器

生产环境

记录错误信息通常使用 error_log() 函数以将错误信息记录到文件系统或syslog.

error_log ( string $message [, int $message_type = 0 [, string $destination [, string $extra_headers ]]] ) : bool

message_type 参数, 设置错误应该发送到何处。可能的信息类型有以下几个:

0 message 发送到 PHP 的系统日志,使用 操作系统的日志机制或者一个文件,取决于 error_log 指令设置了什么。 这是个默认的选项。
1 message 发送到参数 destination 设置的邮件地址。 第四个参数 extra_headers 只有在这个类型里才会被用到。
2 不再是一个选项。
3 message 被发送到位置为 destination 的文件里。 字符 message 不会默认被当做新的一行。
4 message 直接发送到 SAPI 的日志处理程序中。

一个更好的选择是使用 monolog/monolog 扩展包

<?php
require "vendor/autoload.php"; use Monolog\Logger;
use Monolog\Handler\StreamHandler; // create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); // add records to the log
$log->warning('Foo');
$log->error('Bar');

示例: 在生产环境中使用 Monolog 记录日志, 严重错误使用邮件通知

依赖: swiftmailer/swiftmailer

require "vendor/autoload.php";

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SwiftMailerHandler; date_default_timezone_set('Asia/Shanghai'); // 设置monolog
$logger = new Logger('my-app-name');
$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); // 添加SwiftMailer Handler, 遇到严重错误时使用邮件通知
// Create the Transport
$transport = (new Swift_SmtpTransport('smtp.example.org', 25))
->setUsername('your username')
->setPassword('your password'); // Create the Mailer using your created Transport
$mailer = new Swift_Mailer($transport); // Create a message
$message = (new Swift_Message('Wonderful Subject'))
->setFrom(['john@doe.com' => 'John Doe'])
->setTo(['receiver@domain.org', 'other@domain.org' => 'A name']); $logger->pushHandler(new SwiftMailerHandler($mailer, $message, Logger::CRITICAL)); // 使用日志记录器
$logger->critical('The server is on fire!');

php中止时的回调函数

register_shutdown_function

register_shutdown_function(function () {
// do sth...
}, $para1, $param2, ...)

注册一个 callback ,它会在脚本执行完成或者 exit() 后被调用。

Note:

  • 可注册多个回调函数(不会互相覆盖, 依照注册顺序依次调用), 在php脚本中止时会被调用到.

  • 如果在注册的方法内部调用 exit(), 那么所有处理会被中止,并且其他注册的中止回调也不会再被调用。

由于部分错误无法被 set_error_handler 捕获, 因此需配合 register_shutdown_function, 判断脚本退出的原因, 若是因为未被捕获的致命错误, 则需要处理(日志记录等)

register_shutdown_function('handleShutdown');

function handleShutdown()
{
// 如果是因为严重错误(未被捕获)导致脚本退出, 则需要处理(作为对 set_error_handler的补充)
if (! is_null($error = error_get_last()) && isFatal($error['type'])) {
// handleException() 函数同时处理 set_exception_handler
handleException(new \ErrorException(
$error['message'], $error['type'], 0, $error['file'], $error['line'],
));
}
} function isFatal($type)
{
// 以下错误无法被 set_error_handler 捕获: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING
return in_array($type, [E_COMPILE_ERROR, E_CORE_ERROR, E_ERROR, E_PARSE]);
}

Note:

进程被信号 SIGTERM 或 SIGKILL 杀死时中止函数不会被调用. 可通过 pcntl_signal 捕获信号, 再在其中调用 exit() 来进行正常中止.

[原创]PHP 异常错误处理的更多相关文章

  1. [转]ORACLE 异常错误处理

    本文转自:http://www.cnblogs.com/soundcode/archive/2012/01/10/2318385.html 本篇主要内容如下: 5.1 异常处理概念 5.1.1 预定义 ...

  2. [推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼、百战不殆)

    原文:[推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼.百战不殆) [推荐]ORACLE PL/SQL编程之五: 异常错误处理(知已知彼.百战不殆) 继上三篇:ORACLE PL/S ...

  3. 出现java.lang.NoClassDefFoundError: com/google/common/base/Charsets异常错误

    使用selenium,出现java.lang.NoClassDefFoundError: com/google/common/base/Charsets异常错误 原因:selenium-server- ...

  4. 总结Selenium自动化测试方法(六)常见的异常错误处理

    六.常见的异常错误处理 NoSuchElementException: Message: Unable to locate element: {"method":"xpa ...

  5. Java异常错误的面试题及答案

    1) Java中什么是Exception? 这个问题经常在第一次问有关异常的时候或者是面试菜鸟的时候问.我从来没见过面高级或者资深工程师的 时候有人问这玩意,但是对于菜鸟,是很愿意问这个的.简单来说, ...

  6. 总结:整理 oracle异常错误处理 .

    5.1 异常处理概念 5.1.1 预定义的异常处理 5.1.2 非预定义的异常处理 5.1.3 用户自定义的异常处理 5.1.4  用户定义的异常处理 5.2 异常错误传播 5.2.1 在执行部分引发 ...

  7. 解决Python2.7的UnicodeEncodeError:'ascii' codec can't encode characters in position 0-78: ordinal not in range(128)异常错误

    解决Python2.7的UnicodeEncodeError: 'ascii' codec can't encode异常错误 大家都知道,在使用python进行网络爬虫时,最头疼的就是转码问题,下面是 ...

  8. oracle有三种类型的异常错误: 预定义 ( Predefined )错误里面的常见错误

    oracle有三种类型的异常错误: 预定义 ( Predefined )错误, 非预定义 ( Predefined )错误, 用户定义(User_define) 错误 预定义 ( Predefined ...

  9. 整理 oracle异常错误处理

    5.1 异常处理概念 5.1.1 预定义的异常处理 5.1.2 非预定义的异常处理 5.1.3 用户自定义的异常处理 5.1.4  用户定义的异常处理 5.2 异常错误传播 5.2.1 在执行部分引发 ...

随机推荐

  1. Red Hat Cluster Suite 组件 fencing FAQ

    说明 Red Hat Cluster实现HA的关键组件之一是fencing.没有设置fencing,虽然看上去也能够运行Cluster,但是一旦遇到故障切换就会出现异 常,所以深入理解fencing原 ...

  2. CloudStack 初始化执行命令流分析

                查询路由元素   选择可以使用的路由元素   需要将网络服务提供者的:虚拟路由和安全同时启用   { "createnetworkresponse" : { ...

  3. SpringBoot自定义拦截器实现

    1.编写拦截器实现类,此类必须实现接口   HandlerInterceptor,然后重写里面需要的三个比较常用的方法,实现自己的业务逻辑代码 如:OneInterceptor package com ...

  4. GDB调试教程

    简介 GDB(GNU debugger)是GNU开源组织发布的一个强大的UNIX下的程序调试工具.可以使用它通过命令行的方式调试程序.它使你能在程序运行时观察程序的内部结构和内存的使用情况.你也可以使 ...

  5. CircRNA 环化RNA

    2016国自然新秀CircRNA的研究策略和分析  

  6. static 和final

    1.static       static关键字可以用来修饰类的变量,方法和内部类.static是静态的意思,也是全局的意思,它定义的东西属于全局,与类相关,不与具体实例相关.就是说它调用的时候,只是 ...

  7. .NET基础 (06)面向对象的实现

    面向对象的实现1 C#中类可以有多个父类.可以实现多个接口吗2 简述C#中重写.重载和隐藏的概念3 为什么在构造方法中调用虚方法会导致问题4 在C#中如何声明一个类不能被继承 面向对象的实现 1 C# ...

  8. 编写高质量代码改善C#程序的157个建议——建议94:区别对待override和new

    建议94:区别对待override和new override和new使类型体系应为继承而呈现出多态性.多态要求子类具有与基类同名的方法,override和new的作用就是: 如果子类中的方法前面带有n ...

  9. http 错误代码

    2xx  成功  200  正常:请求已完成.  201  正常:紧接 POST 命令.  202  正常:已接受用于处理,但处理尚未完成.  203  正常:部分信息 — 返回的信息只是一部分.   ...

  10. Hortonwork Ambari配置Hive集成Hbase的java开发maven配置

    集群环境 ambari 2.7.3 hdp/hortonwork 2.6.0.3 maven <dependency> <groupId>org.apache.hive< ...