PHP错误与异常(汇总版)

“相信大家都有过这样的体验,无论在开发阶段有多么严谨,都避免不了代码在上线运行过程中发生不可控的错误或异常,导致页面显示达不到预期效果,所以说合理的处理此类情况不但能提高系统的健壮性还有利于我们快速的定位问题”
什么是错误
  • PHP程序自身的问题,一般是由非法的语法,环境问题导致成为错误

    PHP错误配置
  • 全局的配置修改在php.ini文件中,修改后重启php服务生效
    error_reporting = E_ALL  //显示所有错误
    display_errors = Off     //关闭错误提示
    log_errors = On          //错误日志开启
    log_errors_max_len = 1024//设置日志最大长度
    error_log = /error.log   //错误日志文件位置
  • error_reporting
    设置错误报告的级别,该参数可以是一个任意的表示二进制位字段的整数,或者常数名称。错误级别和常数是在 预定义常量定义的,在程序运行时,还可以通过 error_reporting() 函数进行设置
  • 常见级别说明
  • Fatal Error:致命错误(终止运行)
  • Parse Error:编译时解析错误,语法错误(终止运行)
  • Warning Error:警告错误(有出提示信息,不终止运行)
  • Notice Error:通知错误(有出通知信息,不终止运行)

附:官方详细级别说明

错误的处理
  • 众所周知,错误不能被try catch异常类所捕获,不要与异常混淆
  • 兜底处理
  • set_error_handler 附:官方文档戳我
本函数可以用你自己定义的方式来处理运行中的错误, 例如,在应用程序中严重错误发生时,或者在特定条件下触发了一个错误(使用 trigger_error()),你需要对数据/文件做清理回收。
set_error_handler(function ($errno, $errstr, $errfile, $errline, $errcontext = []) {
  $aDataParam = compact('errno', 'errstr', 'errfile', 'errline');
  //TODO 自定义处理:Log记录、邮件提醒、抛出异常记录
  echo "
";
  print_r($aDataParam);exit;
  return false;
}, E_ALL);
test();
//Fatal error: Uncaught Error: Call to undefined function test()
echo $a + 1;
/**
Array
(
    [errno] => 8
    [errstr] => Undefined variable: a
    [errfile] => /opt/homebrew/var/www/php/error/index.php
    [errline] => 126
)
*/
  • 极端兜底处理register_shutdown_function 附:官方文档戳我
    set_error_handler已经捕获到常遇到的大部分错误,未捕获到的输入比较严重的错误了(其实这部分要是能被提到生产环境的话,要好好反省自己了)
    register_shutdown_function(function () {
    $error = error_get_last();
    if($error){
      //记录log,邮件提醒
    }
    });
    test();

    注:PHP脚本执行中可以单独设置,优先级大于php.ini的配置

    ini_set('display_errors', 1); // 配置文件设置了错误不可见, 使用ini_set命令设置可见
    error_reporting(-1); // 设置php错误级别 -1 为显示所有级别的错误
    错误使用的一些准则(非原创)
  • 一定要让 PHP 报告错误
  • 在开发环境中要显示错误
  • 再生产环境中不能显示错误
  • 在开发和生产环境中都要记录错误

    什么是异常
  • 一般是指出现了的不符合预期的情况,但它不是由PHP语法导致的错误;比如:参数不合法的抛出,Curl请求,请求第三方API等

    具体的使用
try {
  if ('他错误') {
    throw new Exception('他找到错了', 0);
  }
   //throw new Exception('他找到错了', 0);
  if ('我错了') {
    throw new Exception('我找到错了', 1);
  }
  //throw new Exception('他找到错了', 0);
  $result = '成功';
} catch (Exception $exception) {
  $result = "错误描述:{$exception->getMessage()},错误代码:{$exception->getCode()}" . PHP_EOL;
} finally {
  Log::info('逻辑处理', ['result' => $result]);
}
  • 剖析一下Exception

    class Exception implements Throwable
    {
      protected $message;                              // 自定义异常提示
      private   $string;                          // __toString 信息
      protected $code;                            // 自定义异常代码
      protected $file;                            // 发生异常的文件名
      protected $line;                            // 发生异常的代码行号
      private   $trace;                           // 数组形式的堆栈跟踪
      private   $previous;                        // 之前抛出的异常
    
      public function __construct($message = null, $code = 0, Exception $previous = null);//异常构造函数
    
      final private function __clone()           // 不能被复制,如果clone异常类将直接产生致命错误
      final public  function getMessage(): string      // 返回异常信息
      final public  function getCode(): int            // 异常克隆
      final public  function getFile(): string         // 返回发生异常的文件名
      final public  function getLine():int             // 返回发生异常的代码行号
      final public  function getTrace(): array         // 获取异常追踪信息
      final public  function getPrevious();            // 之前的 exception
      final public  function getTraceAsString():string // 获取字符串类型的异常追踪信息
      public function __toString():string              // 异常对象转换为字符串
    }
可以看到除了__construct和__toString能被我们重写,其他的方法和部分属性我们都不能做修改,简单复习下final 关键字
  • final 关键字的作用如下:
    使用 final 修饰的类,不能被继承;
    类中使用 final 修饰的成员方法,在子类中不能覆盖(重写)该方法。

    自定义异常
    //IP限制类
    class IpException extends Exception
    {
    public $msg;
    public $code;
    public $param;
    
    public function __construct($message = "", $code = 0, $param = [])
    {
      $this->msg = $message;
      $this->code = $code;
      $this->param = $param;
      parent::__construct($this->msg, $this->msg);
      $this->afterAction();
    }
    
    public function __toString()
    {
      return "非法请求:";
    }
    
    //自定义处理逻辑
    public function afterAction()
    {
      if ($this->code >= 500) {
        Log::ip('重要的记录IP',$this->param);
      }
    }
    }
    
    //验证类异常
    class RuleException extends Exception
    {
    public $msg;
    public $code;
    public $param;
    
    public function __construct($message = "", $code = 0)
    {
      $this->msg = $message;
      $this->code = $code;
      parent::__construct($this->msg, $this->msg);
    }
    public function __toString()
    {
      return "非法请求:";
    }
    }
    //兜底异常,无对应try()catch捕获时,会进入到自定义处理的方法
    set_exception_handler('exception_handler');
    function exception_handler($exception) {
    var_dump($exception);
    }
    
    try {
    if ('IP不对') {
      throw new IpException('ip被限制', 500, ['ip' => '0.0.0.0']);
    }
    if ('uid不对') {
      throw new RuleException('缺少uid');
    }
    } catch (RuleException $ruleException) {
    echo $ruleException . $ruleException->getMessage();
    } catch (IpException $ipException) {
    echo $ipException . $ipException->getMessage();
    }
  • set_exception_handler()

    设置默认的异常处理程序,用于没有用 try/catch 块来捕获的异常。 在 exception_handler 调用后异常会中止。
  • 自定义异常作用(非原创)

    • 开闭原则对接口编程,依赖于抽象而不依赖于具体(继承Exception后的各sonException可以实现更多的按需原则)
    • 迪米特法则(最少知道原则)一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
    php7小技巧

    Throwable 配合下方的层级图可更好的理解
    附:官方解释戳我
    PHP错误与异常(汇总版)_第1张图片

//php5
try
{
  //业务出错
}catch (Exception $exception)
{
    //捕获异常出错
}
catch (Error $error)
{
   //捕获基本错误
}

//>php7
try
{
  //业务出错
}
catch (Throwable $t)
{
  //捕获异常出错,捕获基本错误
}
总结

通过上边的介绍相信大家对php的错误和异常有了更深一步的了解,平常开发中总是使用它们,而不能发挥出它们的最佳效果,这也是一件比较悲伤的事情;so,赶快行动起来吧!

参考文档:

laravel异常分析文档
PHP 最佳实践之异常和错误
PHP错误和异常详解(PHP7错误处理)
PHP 错误与异常