错误处理

CodeIgniter 通过“异常(Exception)”在你的系统中内置了错误报告,包括 SPL 集合,以及框架提供的一些“异常”。

取决于你的环境设置,当抛出错误或异常时的默认操作是显示详细的错误报告,除非应用程序在 production 环境下运行。 在 production 环境中,会显示更通用的消息以对用户保持最佳体验。

使用异常

本节简要概述了对异常不太了解的新程序员或开发人员的情况。

什么是异常

异常简单来说就是在抛出异常时发生的事件。这将中止脚本的当前流程,然后执行将转移到错误处理程序,后者将显示适当的错误页面:

<?php

throw new \Exception('Some message goes here');

捕获异常

如果你正在调用可能抛出异常的方法,你可以使用 try/catch 块捕获该异常:

<?php

try {
    $user = $userModel->find($id);
} catch (\Exception $e) {
    exit($e->getMessage());
}

如果 $userModel 抛出异常,则会捕获它并执行 catch 块中的代码。在这个例子中,脚本终止,并回显 UserModel 定义的错误信息。

捕获特定异常

在上面的示例中,我们捕获任何类型的异常。如果我们只想监视特定类型的异常,如 DataException,我们可以在 catch 参数中指定它。任何其他抛出的不属于捕获的异常子类的异常都将传递给错误处理程序:

<?php

use CodeIgniter\Database\Exceptions\DataException;

try {
    $user = $userModel->find($id);
} catch (DataException $e) {
    // do something here...
}

这在自己处理错误或在脚本结束前执行清理时很有用。如果你想要错误处理程序正常工作,你可以在 catch 块内抛出一个新异常:

<?php

use CodeIgniter\Database\Exceptions\DataException;

try {
    $user = $userModel->find($id);
} catch (DataException $e) {
    // do something here...

    throw new \RuntimeException($e->getMessage(), $e->getCode(), $e);
}

配置

错误报告

默认情况下,CodeIgniter 在 developmenttesting 环境下会显示包含所有错误的详细错误报告,并且在 production 环境下不会显示任何错误。

../_images/error.png

你可以通过设置 CI_ENVIRONMENT 变量来更改你的环境。请参阅 设置环境

重要

禁用错误报告并不会停止在错误发生时写入日志。

警告

请注意,.env 文件中的设置会添加到 $_SERVER$_ENV 中。作为副作用,这意味着如果显示详细的错误报告,你的安全凭据将被公开

记录异常

默认情况下,除了“404 - Page Not Found”异常之外的所有异常都会记录日志。这可以通过设置 app/Config/Exceptions.php$log 值来打开和关闭:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Exceptions extends BaseConfig
{
    // ...
    public bool $log = true;
    // ...
}

要忽略其他状态码的日志记录,可以在同一文件中设置要忽略的状态码:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Exceptions extends BaseConfig
{
    // ...
    public array $ignoreCodes = [404];
    // ...
}

备注

如果你的当前 日志配置 没有设置记录 critical 错误的话,异常仍然可能不会被记录,因为所有的异常都作为 critical 错误来记录。

记录弃用警告

在 4.3.0 版本加入.

在 v4.3.0 之前,所有通过 error_reporting() 报告的错误都会被抛出为一个 ErrorException 对象。

但随着 PHP 8.1+ 的广泛使用,许多用户可能会遇到因为 passing null to non-nullable arguments of internal functions 而抛出的异常。

为了更顺利地迁移到 PHP 8.1,从 v4.3.0 开始,CodeIgniter 增加了仅记录弃用错误(E_DEPRECATEDE_USER_DEPRECATED)而不将其作为异常抛出的功能。

默认情况下,CodeIgniter 在开发环境中仅记录弃用警告而不抛出异常。在生产环境中,则不进行记录且不抛出异常。

配置

此功能的设置如下。首先,确保你的 Config\Exceptions 副本更新了两个新属性,并按以下方式设置:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

class Exceptions extends BaseConfig
{
    // ...
    public bool $logDeprecations = true; // If set to false, an exception will be thrown.
    // ...
    public string $deprecationLogLevel = LogLevel::WARNING; // This should be one of the log levels supported by PSR-3.
    // ...
}

接下来,根据你在 Config\Exceptions::$deprecationLogLevel 中设置的日志级别,检查 Config\Logger::$threshold 中定义的日志门槛是否涵盖了弃用日志级别。如果没有,请相应调整。

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Logger extends BaseConfig
{
    // ...
    // This must contain the log level (5 for LogLevel::WARNING) corresponding to $deprecationLogLevel.
    public $threshold = (ENVIRONMENT === 'production') ? 4 : 9;
    // ...
}

之后,后续的弃用警告将按照配置进行记录,而不会作为异常抛出。

此功能也适用于用户弃用警告:

<?php

@trigger_error('Do not use this class!', E_USER_DEPRECATED);
// Your logs should contain a record with a message like: "[DEPRECATED] Do not use this class!"

对于测试你的应用程序,你可能希望总是抛出弃用警告。你可以通过将环境变量 CODEIGNITER_SCREAM_DEPRECATIONS 设置为真值来配置这一点。

框架异常

以下框架异常可用:

PageNotFoundException

这用于表示 404,页面未找到错误:

<?php

use CodeIgniter\Exceptions\PageNotFoundException;

$page = $pageModel->find($id);

if ($page === null) {
    throw PageNotFoundException::forPageNotFound();
}

你可以传入一个消息到异常中,它将显示在404页面上的默认消息位置:

有关默认的 404 视图文件位置,请参见 HTTP 状态码和错误视图

如果你在 app/Config/Routing.phpapp/Config/Routes.php 中指定了 404 重写,那么将会调用这个覆盖页面,而不是标准的 404 页面。

ConfigException

当配置类的值无效时,或者配置类不是正确的类型时,应使用此异常:

<?php

throw new \CodeIgniter\Exceptions\ConfigException();

这提供退出代码 3。

DatabaseException

此异常用于数据库错误,例如无法创建数据库连接或连接暂时丢失时:

<?php

throw new \CodeIgniter\Database\Exceptions\DatabaseException();

这提供退出代码 8。

RedirectException

备注

自 v4.4.0 起,RedirectException 的命名空间已更改。之前是 CodeIgniter\Router\Exceptions\RedirectException。之前的类已被弃用。

此异常是一个特殊情况,允许覆盖所有其他响应路由并强制重定向到特定的 URI:

<?php

throw new \CodeIgniter\HTTP\Exceptions\RedirectException($uri);

$uri 是相对于 baseURL 的 URI 路径。你还可以提供一个重定向代码,以替代默认值 (302, “temporary redirect”):

<?php

throw new \CodeIgniter\HTTP\Exceptions\RedirectException($uri, 301);

另外,自 v4.4.0 版本开始,可以将实现了 ResponseInterface 接口的类的对象用作第一个参数。这种解决方案适用于需要在响应中添加额外的头部或 Cookie 的情况。

<?php

$response = service('response')
    ->redirect('https://example.com/path')
    ->setHeader('Some', 'header')
    ->setCookie('and', 'cookie');

throw new \CodeIgniter\HTTP\Exceptions\RedirectException($response);

在异常中指定 HTTP 状态码

在 4.3.0 版本加入.

从 v4.3.0 开始,你可以为异常类指定 HTTP 状态码来实现 CodeIgniter\Exceptions\HTTPExceptionInterface

当 CodeIgniter 的异常处理程序捕获实现了 HTTPExceptionInterface 的异常时,异常代码将成为 HTTP 状态码。

HTTP 状态码和错误视图

异常处理程序会显示对应于 HTTP 状态码的错误视图(如果存在的话)。

例如,PageNotFoundException 实现了 HTTPExceptionInterface,所以它的异常代码 404 将成为 HTTP 状态码。因此,如果它被抛出,系统将在处理网页请求时显示 app/Views/errors/html 文件夹中的 error_404.php。如果是通过 CLI 调用,系统将显示 app/Views/errors/cli 文件夹中的 error_404.php

如果没有与 HTTP 状态码对应的视图文件,那么将显示 production.phperror_exception.php

备注

如果在 PHP INI 配置中开启了 display_errors,将选择 error_exception.php 并显示详细的错误报告。

你应该自定义 app/Views/errors/html 文件夹中的所有错误视图以适应你的站点。

你还可以为特定的 HTTP 状态码创建错误视图。例如,如果你想创建一个 “400 Bad Request” 的错误视图,添加 error_400.php

警告

如果存在对应 HTTP 状态码的错误视图文件,异常处理程序将无论环境如何显示该文件。视图文件必须自行实现,不在生产环境中显示详细的错误信息。

在你的异常中指定退出代码

在 4.3.0 版本加入.

自 v4.3.0 起,你可以为你的异常类指定退出代码,以实现 CodeIgniter\Exceptions\HasExitCodeInterface

当实现了 HasExitCodeInterface 的异常被 CodeIgniter 的异常处理程序捕获时,从 getExitCode() 方法返回的代码将成为退出代码。

自定义异常处理程序

在 4.4.0 版本加入.

如果你需要更多地控制异常的显示方式,现在可以定义自己的处理程序并指定它们适用的情况。

定义新的处理程序

第一步是创建一个新的类,该类实现了 CodeIgniter\Debug\ExceptionHandlerInterface 接口。你还可以扩展 CodeIgniter\Debug\BaseExceptionHandler 类。该类包含了许多在默认异常处理程序中使用的实用方法。新的处理程序必须实现一个方法:handle()

<?php

namespace App\Libraries;

use CodeIgniter\Debug\BaseExceptionHandler;
use CodeIgniter\Debug\ExceptionHandlerInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Throwable;

class MyExceptionHandler extends BaseExceptionHandler implements ExceptionHandlerInterface
{
    // You can override the view path.
    protected ?string $viewPath = APPPATH . 'Views/exception/';

    public function handle(
        Throwable $exception,
        RequestInterface $request,
        ResponseInterface $response,
        int $statusCode,
        int $exitCode
    ): void {
        $this->render($exception, $statusCode, $this->viewPath . "error_{$statusCode}.php");

        exit($exitCode);
    }
}

这个示例定义了通常需要的最少代码 - 显示一个视图并使用适当的退出代码退出。然而,BaseExceptionHandler 提供了许多其他的辅助函数和对象。

配置新的处理程序

告诉 CodeIgniter 使用你的新异常处理程序类是在 app/Config/Exceptions.php 配置文件的 handler() 方法中完成的:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Debug\ExceptionHandler;
use CodeIgniter\Debug\ExceptionHandlerInterface;
use Throwable;

class Exceptions extends BaseConfig
{
    // ...

    public function handler(int $statusCode, Throwable $exception): ExceptionHandlerInterface
    {
        return new ExceptionHandler($this);
    }
}

你可以使用任何逻辑来确定应用程序是否应该处理异常,但最常见的两种情况是检查 HTTP 状态码或异常的类型。如果你的类应该处理它,则返回一个新的实例:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Debug\ExceptionHandlerInterface;
use CodeIgniter\Exceptions\PageNotFoundException;
use Throwable;

class Exceptions extends BaseConfig
{
    // ...

    public function handler(int $statusCode, Throwable $exception): ExceptionHandlerInterface
    {
        if (in_array($statusCode, [400, 404, 500], true)) {
            return new \App\Libraries\MyExceptionHandler($this);
        }

        if ($exception instanceof PageNotFoundException) {
            return new \App\Libraries\MyExceptionHandler($this);
        }

        return new \CodeIgniter\Debug\ExceptionHandler($this);
    }
}