错误处理

CodeIgniter 通过异常构建错误报告系统,包括 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 定义的错误消息。

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

<?php

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

这对于自己处理错误或在脚本结束之前执行清理非常有用。如果您希望错误处理程序按正常方式运行,您可以在 catch 块中抛出一个新的异常。

<?php

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

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

配置

错误报告

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

../_images/error.png

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

重要

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

警告

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

记录异常

默认情况下,除了 404 - 页面未找到异常之外的所有异常都会被记录。这可以通过设置 app/Config/Exceptions.php$log 值来开启或关闭。

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

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

要忽略其他状态码的记录,您可以在同一个文件中设置要忽略的状态码。

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Exceptions extends BaseConfig
{
    public $ignoredCodes = [404];
}

注意

如果您的当前日志设置没有设置为记录 critical 错误(所有异常都记录为 critical 错误),那么异常的记录可能仍然不会发生。

框架异常

以下框架异常可用

PageNotFoundException

这用于表示 404,页面未找到错误。当抛出时,系统将显示在 app/Views/errors/html/error_404.php 中找到的视图。您应该为您的网站自定义所有错误视图。如果在 app/Config/Routes.php 中,您指定了 404 覆盖,则将调用该覆盖而不是标准 404 页面。

<?php

if (! $page = $pageModel->find($id)) {
    throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}

您可以将消息传递到异常中,该消息将在 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,"临时重定向")

<?php

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

此外,从 v4.4.0 开始,可以将实现 ResponseInterface 的类的对象用作第一个参数。此解决方案适用于您需要在响应中添加其他标头或 cookie 的情况。

<?php

$response = \Config\Services::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 开始,您可以指定异常类以实现 HTTPExceptionInterface 的 HTTP 状态代码。

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

在异常中指定退出代码

版本 4.3.0 中的新增功能。

从 v4.3.0 开始,您可以指定异常类以实现 HasExitCodeInterface 的退出代码。

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

记录弃用警告

版本 4.3.0 中的新增功能。

默认情况下,由 error_reporting() 报告的所有错误都将作为 ErrorException 对象抛出。这些包括 E_DEPRECATEDE_USER_DEPRECATED 错误。随着 PHP 8.1+ 的使用激增,许多用户可能会看到为 将 null 传递给内部函数的非空参数 抛出的异常。为了简化迁移到 PHP 8.1,您可以指示 CodeIgniter 记录弃用,而不是抛出它们。

首先,确保您的 Config\Exceptions 副本已更新为包含两个新属性,并按如下方式设置。

<?php

namespace Config;

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

class Exceptions extends BaseConfig
{
    // ... other properties

    public bool $logDeprecations       = true;
    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
{
    // .. other properties

    public $threshold = 5; // originally 4 but changed to 5 to log the warnings from the deprecations
}

之后,后续的弃用将被记录而不是抛出。

此功能也适用于用户弃用。

<?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 设置为真值来配置此选项。

自定义异常处理程序

版本 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 提供了许多其他辅助函数和对象。

配置新的处理程序

在 **app/Config/Exceptions.php** 配置文件的 handler() 方法中告诉 CodeIgniter 使用您的新异常处理程序类。

<?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);
    }
}