Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 564|回复: 0

[原创]PHP 异常错误处理

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-7-22 13:29:49 | 显示全部楼层 |阅读模式

    错误与异常

    错误异常 在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() 来进行正常中止.

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-28 06:27 , Processed in 0.096247 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表