错误和异常处理

CakePHP 应用程序为您设置了错误和异常处理。PHP 错误会被捕获并显示或记录。未捕获的异常会自动渲染成错误页面。

配置

错误配置是在应用程序的 config/app.php 文件中完成的。默认情况下,CakePHP 使用 Cake\Error\ErrorTrapCake\Error\ExceptionTrap 分别处理 PHP 错误和异常。错误配置允许您为应用程序自定义错误处理。支持以下选项

  • errorLevel - int - 您感兴趣的捕获错误级别。使用内置的 PHP 错误常量和位掩码来选择您感兴趣的错误级别。请参阅 弃用警告 以禁用弃用警告。

  • trace - bool - 在日志文件中包含错误的堆栈跟踪。堆栈跟踪将在每次错误之后包含在日志中。这有助于查找错误发生的位置和时间。

  • exceptionRenderer - string - 用于渲染未捕获异常的类。如果您选择自定义类,则应将该类的文件放置在 src/Error 中。此类需要实现一个 render() 方法。

  • log - bool - 当 true 时,异常及其堆栈跟踪将被记录到 Cake\Log\Log

  • skipLog - array - 不应记录的异常类名的数组。这对于删除 NotFoundExceptions 或其他常见但无趣的日志消息很有用。

  • extraFatalErrorMemory - int - 设置为遇到致命错误时增加的内存限制的兆字节数。这允许在完成日志记录或错误处理时提供空间。

  • logger(在 4.4.0 之前使用 errorLogger) - Cake\Error\ErrorLoggerInterface - 负责记录错误和未处理异常的类。默认设置为 Cake\Error\ErrorLogger

  • errorRenderer - Cake\Error\ErrorRendererInterface - 负责渲染错误的类。默认值根据 PHP SAPI 选择。

  • ignoredDeprecationPaths - array - 一个 glob 兼容路径列表,这些路径上的弃用错误应该被忽略。在 4.2.0 中添加。

默认情况下,当 debugtrue 时,PHP 错误会被显示,而当 debug 为 false 时,PHP 错误会被记录。致命错误处理程序将独立于 debug 级别或 errorLevel 配置被调用,但结果将根据 debug 级别而不同。致命错误的默认行为是显示一个内部服务器错误页面 (debug 禁用) 或一个包含消息、文件和行号的页面 (debug 启用)。

注意

如果您使用自定义错误处理程序,支持的选项将取决于您的处理程序。

弃用警告

CakePHP 使用弃用警告来指示功能何时被弃用。我们还建议在您的插件和应用程序代码中有用时使用此系统。您可以使用 deprecationWarning() 触发弃用警告。

deprecationWarning('5.0', 'The example() method is deprecated. Use getExample() instead.');

升级 CakePHP 或插件时,您可能会遇到新的弃用警告。您可以通过以下几种方法暂时禁用弃用警告。

  1. Error.errorLevel 设置设置为 E_ALL ^ E_USER_DEPRECATED 以忽略所有弃用警告。

  2. 使用 Error.ignoredDeprecationPaths 配置选项来忽略具有 glob 兼容表达式的弃用。例如:

    'Error' => [
        'ignoredDeprecationPaths' => [
            'vendors/company/contacts/*',
            'src/Models/*',
        ],
    ],
    

    将忽略来自应用程序 Models 目录和 Contacts 插件的所有弃用。

更改异常处理

CakePHP 中的异常处理提供了多种方法来定制异常的处理方式。每种方法都为您提供了不同程度的异常处理过程控制权。

  1. 监听事件 这使您能够通过 CakePHP 事件在处理错误和异常时收到通知。

  2. 自定义模板 这使您可以更改渲染的视图模板,就像您在应用程序中更改任何其他模板一样。

  3. 自定义控制器 这使您可以控制异常页面的渲染方式。

  4. 自定义异常渲染器 这使您可以控制异常页面和日志记录的执行方式。

  5. 创建和注册您自己的陷阱 这使您能够完全控制错误和异常的处理、记录和渲染方式。在实现您的陷阱时,请参考 Cake\Error\ExceptionTrapCake\Error\ErrorTrap

监听事件

ErrorTrapExceptionTrap 处理程序将在它们处理错误时触发 CakePHP 事件。您可以监听 Error.beforeRender 事件以收到 PHP 错误的通知。当处理异常时,会分派 Exception.beforeRender 事件。

$errorTrap = new ErrorTrap(Configure::read('Error'));
$errorTrap->getEventManager()->on(
    'Error.beforeRender',
    function (EventInterface $event, PhpError $error) {
        // do your thing
    }
);

Error.beforeRender 处理程序中,您有几个选项:

  • 停止事件以阻止渲染。

  • 返回一个字符串以跳过渲染并使用提供的字符串代替。

Exception.beforeRender 处理程序中,您有几个选项:

  • 停止事件以阻止渲染。

  • 使用 setData('exception', $err) 设置 exception 数据属性以替换正在渲染的异常。

  • 从事件监听器返回一个响应以跳过渲染并使用提供的响应代替。

自定义模板

默认异常陷阱使用 Cake\Error\Renderer\WebExceptionRenderer 和您的应用程序 ErrorController 来呈现应用程序引发的所有未捕获异常。

错误页面视图位于 **templates/Error/**。所有 4xx 错误使用 **error400.php** 模板,而 5xx 错误使用 **error500.php**。您的错误模板将可以使用以下变量

  • message 异常消息。

  • code 异常代码。

  • url 请求 URL。

  • error 异常对象。

在调试模式下,如果您的错误扩展了 Cake\Core\Exception\CakeException,则由 getAttributes() 返回的数据也将作为视图变量公开。

注意

您需要将 debug 设置为 false,才能看到您的 **error404** 和 **error500** 模板。在调试模式下,您将看到 CakePHP 的开发错误页面。

自定义错误页面布局

默认情况下,错误模板使用 **templates/layout/error.php** 作为布局。您可以使用 layout 属性选择不同的布局

// inside templates/Error/error400.php
$this->layout = 'my_error';

以上将使用 **templates/layout/my_error.php** 作为错误页面的布局。

CakePHP 引发的许多异常将在调试模式下呈现特定的视图模板。在调试关闭的情况下,CakePHP 引发的所有异常将根据其状态代码使用 **error400.php** 或 **error500.php**。

自定义控制器

App\Controller\ErrorController 类由 CakePHP 的异常呈现使用,用于呈现错误页面视图并接收所有标准请求生命周期事件。通过修改此类,您可以控制使用哪些组件以及呈现哪些模板。

如果您的应用程序使用 前缀路由,您可以为每个路由前缀创建自定义错误控制器。例如,如果您有一个 Admin 前缀。您可以创建以下类

namespace App\Controller\Admin;

use App\Controller\AppController;
use Cake\Event\EventInterface;

class ErrorController extends AppController
{
    /**
     * beforeRender callback.
     *
     * @param \Cake\Event\EventInterface $event Event.
     * @return void
     */
    public function beforeRender(EventInterface $event)
    {
        $this->viewBuilder()->setTemplatePath('Error');
    }
}

此控制器仅在带前缀的控制器中遇到错误时使用,并允许您根据需要定义特定于前缀的逻辑/模板。

自定义异常呈现器

如果要控制整个异常呈现和日志记录过程,可以使用 **config/app.php** 中的 Error.exceptionRenderer 选项来选择将呈现异常页面的类。更改异常呈现器在您想要更改用于创建错误控制器的逻辑、选择模板或控制总体呈现过程时很有用。

您的自定义异常呈现器类应放置在 **src/Error** 中。假设我们的应用程序使用 App\Exception\MissingWidgetException 来指示缺少小部件。我们可以创建一个异常呈现器,在处理此错误时呈现特定的错误页面

// In src/Error/AppExceptionRenderer.php
namespace App\Error;

use Cake\Error\Renderer\WebExceptionRenderer;

class AppExceptionRenderer extends WebExceptionRenderer
{
    public function missingWidget($error)
    {
        $response = $this->controller->getResponse();

        return $response->withStringBody('Oops that widget is missing.');
    }
}

// In config/app.php
'Error' => [
    'exceptionRenderer' => 'App\Error\AppExceptionRenderer',
    // ...
],
// ...

以上将处理我们的 MissingWidgetException,并允许我们为这些应用程序异常提供自定义显示/处理逻辑。

异常呈现方法将处理的异常作为参数接收,并应返回一个 Response 对象。您还可以实现方法,以便在处理 CakePHP 错误时添加其他逻辑

// In src/Error/AppExceptionRenderer.php
namespace App\Error;

use Cake\Error\Renderer\WebExceptionRenderer;

class AppExceptionRenderer extends WebExceptionRenderer
{
    public function notFound($error)
    {
        // Do something with NotFoundException objects.
    }
}

更改 ErrorController 类

异常呈现器决定使用哪个控制器进行异常呈现。如果要更改用于呈现异常的控制器,请在您的异常呈现器中覆盖 _getController() 方法

// in src/Error/AppExceptionRenderer
namespace App\Error;

use App\Controller\SuperCustomErrorController;
use Cake\Controller\Controller;
use Cake\Error\Renderer\WebExceptionRenderer;

class AppExceptionRenderer extends WebExceptionRenderer
{
    protected function _getController(): Controller
    {
        return new SuperCustomErrorController();
    }
}

// in config/app.php
'Error' => [
    'exceptionRenderer' => 'App\Error\AppExceptionRenderer',
    // ...
],
// ...

创建您自己的应用程序异常

您可以使用任何内置的 SPL 异常Exception 本身或 Cake\Core\Exception\Exception 来创建您自己的应用程序异常。如果您的应用程序包含以下异常

use Cake\Core\Exception\CakeException;

class MissingWidgetException extends CakeException
{
}

您可以通过创建 **templates/Error/missing_widget.php** 来提供良好的开发错误。在生产模式下,上面的错误将被视为 500 错误并使用 **error500** 模板。

扩展 Cake\Http\Exception\HttpException 的异常,如果错误代码介于 400506 之间,它们的错误代码将用作 HTTP 状态代码。

Cake\Core\Exception\CakeException 的构造函数允许您传入额外的數據。这些附加数据会插入到 _messageTemplate 中。这允许您创建数据丰富的异常,这些异常提供更多关于错误的上下文

use Cake\Core\Exception\CakeException;

class MissingWidgetException extends Exception
{
    // Context data is interpolated into this format string.
    protected $_messageTemplate = 'Seems that %s is missing.';

    // You can set a default exception code as well.
    protected $_defaultCode = 404;
}

throw new MissingWidgetException(['widget' => 'Pointy']);

呈现时,您的视图模板将设置一个 $widget 变量。如果将异常转换为字符串或使用其 getMessage() 方法,您将获得 Seems that Pointy is missing.

注意

在 CakePHP 4.2.0 之前,请使用类 Cake\Core\Exception\Exception 而不是 Cake\Core\Exception\CakeException

记录异常

使用内置的异常处理,您可以通过在 **config/app.php** 中将 log 选项设置为 true 来记录 ErrorTrap 处理的所有异常。启用此功能会将每个异常记录到 Cake\Log\Log 和配置的记录器中。

注意

如果您使用的是自定义异常处理程序,则此设置将无效。除非您在实现中引用它。

CakePHP 的内置异常

HTTP 异常

CakePHP 中有几个内置异常,除了内部框架异常之外,还有几个用于 HTTP 方法的异常

exception Cake\Http\Exception\BadRequestException

用于执行 400 错误请求错误。

exception Cake\Http\Exception\UnauthorizedException

用于执行 401 未授权错误。

exception Cake\Http\Exception\ForbiddenException

用于执行 403 禁止错误。

exception Cake\Http\Exception\InvalidCsrfTokenException

用于执行由无效 CSRF 令牌引起的 403 错误。

exception Cake\Http\Exception\NotFoundException

用于执行 404 未找到错误。

exception Cake\Http\Exception\MethodNotAllowedException

用于执行 405 方法不允许错误。

exception Cake\Http\Exception\NotAcceptableException

用于执行 406 不可接受错误。

exception Cake\Http\Exception\ConflictException

用于执行 409 冲突错误。

exception Cake\Http\Exception\GoneException

用于执行 410 已消失错误。

有关 HTTP 4xx 错误状态代码的更多详细信息,请参阅 RFC 2616 第 10.4 节

exception Cake\Http\Exception\InternalErrorException

用于执行 500 内部服务器错误。

exception Cake\Http\Exception\NotImplementedException

用于执行 501 未实现错误。

exception Cake\Http\Exception\ServiceUnavailableException

用于执行 503 服务不可用错误。

有关 HTTP 5xx 错误状态代码的更多详细信息,请参阅 RFC 2616 第 10.5 节

您可以从控制器中抛出这些异常来指示失败状态或 HTTP 错误。 HTTP 异常的一个示例用法可能是为未找到的项目渲染 404 页面。

use Cake\Http\Exception\NotFoundException;

public function view($id = null)
{
    $article = $this->Articles->findById($id)->first();
    if (empty($article)) {
        throw new NotFoundException(__('Article not found'));
    }
    $this->set('article', $article);
    $this->viewBuilder()->setOption('serialize', ['article']);
}

通过使用异常来处理 HTTP 错误,您可以保持代码清洁,并为客户端应用程序和用户提供 RESTful 响应。

在控制器中使用 HTTP 异常

您可以从控制器操作中抛出任何与 HTTP 相关的异常来指示失败状态。 例如

use Cake\Network\Exception\NotFoundException;

public function view($id = null)
{
    $article = $this->Articles->findById($id)->first();
    if (empty($article)) {
        throw new NotFoundException(__('Article not found'));
    }
    $this->set('article', 'article');
    $this->viewBuilder()->setOption('serialize', ['article']);
}

上面的操作将导致配置的异常处理程序捕获并处理 NotFoundException。 默认情况下,这将创建一个错误页面并记录异常。

其他内置异常

此外,CakePHP 使用以下异常

exception Cake\View\Exception\MissingViewException

找不到选择的视图类。

exception Cake\View\Exception\MissingTemplateException

找不到选择的模板文件。

exception Cake\View\Exception\MissingLayoutException

找不到选择的布局。

exception Cake\View\Exception\MissingHelperException

找不到选择的辅助程序。

exception Cake\View\Exception\MissingElementException

找不到选择的元素文件。

exception Cake\View\Exception\MissingCellException

找不到选择的单元格类。

exception Cake\View\Exception\MissingCellViewException

找不到选择的单元格视图文件。

exception Cake\Controller\Exception\MissingComponentException

找不到配置的组件。

exception Cake\Controller\Exception\MissingActionException

找不到请求的控制器操作。

exception Cake\Controller\Exception\PrivateActionException

访问私有/受保护的/_ 前缀操作。

exception Cake\Console\Exception\ConsoleException

控制台库类遇到错误。

exception Cake\Database\Exception\MissingConnectionException

模型的连接丢失。

exception Cake\Database\Exception\MissingDriverException

找不到数据库驱动程序。

exception Cake\Database\Exception\MissingExtensionException

数据库驱动程序缺少 PHP 扩展。

exception Cake\ORM\Exception\MissingTableException

找不到模型的表格。

exception Cake\ORM\Exception\MissingEntityException

找不到模型的实体。

exception Cake\ORM\Exception\MissingBehaviorException

找不到模型的行为。

exception Cake\ORM\Exception\PersistenceFailedException

在使用 Cake\ORM\Table::saveOrFail()Cake\ORM\Table::deleteOrFail() 时,实体无法保存/删除。

exception Cake\Datasource\Exception\RecordNotFoundException

找不到请求的记录。 这也会将 HTTP 响应头设置为 404。

exception Cake\Routing\Exception\MissingControllerException

找不到请求的控制器。

exception Cake\Routing\Exception\MissingRouteException

请求的 URL 无法反向路由或无法解析。

exception Cake\Core\Exception\Exception

CakePHP 中的基类异常。 CakePHP 抛出的所有框架层异常都将扩展此类。

这些异常类都扩展了 Exception。 通过扩展 Exception,您可以创建自己的“框架”错误。

Cake\Core\Exception\Exception::responseHeader($header = null, $value = null)

参见 Cake\Network\Request::header()

所有 Http 和 Cake 异常都扩展了 Exception 类,该类有一个方法可以将头添加到响应中。 例如,当抛出 405 MethodNotAllowedException 时,rfc2616 指出

"The response MUST include an Allow header containing a list of valid
methods for the requested resource."

自定义 PHP 错误处理

默认情况下,PHP 错误会渲染到控制台或 HTML 输出中,还会记录下来。 如果需要,您可以用自己的代码替换 CakePHP 的错误处理逻辑。

自定义错误日志记录

错误处理程序使用 Cake\Error\ErrorLoggingInterface 的实例来创建日志消息并将它们记录到适当的位置。 您可以使用 Error.errorLogger 配置值替换错误记录器。 一个示例错误记录器

namespace App\Error;

use Cake\Error\ErrorLoggerInterface;
use Cake\Error\PhpError;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;

/**
 * Log errors and unhandled exceptions to `Cake\Log\Log`
 */
class ErrorLogger implements ErrorLoggerInterface
{
    /**
     * @inheritDoc
     */
    public function logError(
        PhpError $error,
        ?ServerRequestInterface $request,
        bool $includeTrace = false
    ): void {
        // Log PHP Errors
    }

    /**
     * @inheritDoc
     */
    public function logException(
        ?ServerRequestInterface $request,
        bool $includeTrace = false
    ): void {
        // Log exceptions.
    }
}

自定义错误渲染

CakePHP 包含用于 Web 和控制台环境的错误渲染器。 但是,如果您想替换渲染错误的逻辑,您可以创建一个类

// src/Error/CustomErrorRenderer.php
namespace App\Error;

use Cake\Error\ErrorRendererInterface;
use Cake\Error\PhpError;

class CustomErrorRenderer implements ErrorRendererInterface
{
    public function write(string $out): void
    {
        // output the rendered error to the appropriate output stream
    }

    public function render(PhpError $error, bool $debug): string
    {
        // Convert the error into the output string.
    }
}

您的渲染器构造函数将传递一个包含所有 Error 配置的数组。 您可以通过 Error.errorRenderer 配置值将自定义错误渲染器连接到 CakePHP。 替换错误处理时,您需要同时考虑 Web 和命令行环境。