如何自定义错误页

3.4 版本
维护中的版本

在symfony应用程序中,所有的错误都被当作异常,无论是404 Not Found错误,还是通过在你的代码中抛出一些异常而触发一个致命错误。

开发环境中,Symfony会捕获所有的异常并通过一个拥有很多调试信息的异常页来帮助你快速发现问题的根源:

exceptions-in-dev-environment

由于这个页面包含了很多敏感的内部信息,Symfony 不会将其在生产环境中展示。作为替代,会显示一个简单而普通的错误页面:

errors-in-prod-environment

生产环境下的错误页面,可以按照你的要求进行各种自定义:

  1. 如果你只想更改错误页的内容和样式,来与你的应用程序的其余部分相匹配,你可以覆写默认的错误模板

  2. 如果你还想去调整用于symfony生成错误页面的逻辑,你可以覆写默认的异常控制器

  3. 如果你需要完全控制异常处理来执行自己的逻辑,你需要使用kernel.exception事件

覆写默认的错误模板 

当加载错误页面时,一个内部的ExceptionController将被使用,来渲染一个TWIG模板并显示给用户。

该控制器使用HTTP状态码、请求格式和以下逻辑来决定模板名称:

  1. 寻找一个给定格式和状态码的模板(像,error404.json.twigerror500.html.twig);

  2. 如果上面的模板不存在,就放弃状态码并寻找一个给定格式的普通模板(像,error.json.twigerror.xml.twig);

  3. 如果上述所说的模板都不存在,那就回退使用普通的HTML 模板(error.html.twig)。

要复写这些模板,只需根据标准symfony方式复写Bundle内的模板来操作:把模板放入app/Resources/TwigBundle/views/Exception/目录中。

项目中的一个典型的返回HTML或JOSN的页面,可能像下面这样:

1
2
3
4
5
6
7
8
9
10
11
app/
└─ Resources/
   └─ TwigBundle/
      └─ views/
         └─ Exception/
            ├─ error404.html.twig
            ├─ error403.html.twig
            ├─ error.html.twig      # All other HTML errors (including 500)
            ├─ error404.json.twig
            ├─ error403.json.twig
            └─ error.json.twig      # All other JSON errors (including 500)

404错误模板样例 

要覆写404错误的html模板,创建一个位于app/Resources/TwigBundle/views/Exception/的新的error404.html.twig模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{# app/Resources/TwigBundle/views/Exception/error404.html.twig #}
{% extends 'base.html.twig' %}
 
{% block body %}
    <h1>Page not found</h1>
 
    {% if is_granted('IS_AUTHENTICATED_FULLY') %}
        {# ... #}
    {% endif %}
 
    <p>
        The requested page couldn't be located. Checkout for any URL
        misspelling or <a href="{{ path('homepage') }}">return to the homepage</a>.
    </p>
{% endblock %}

若你需要,ExceptionController 会传入一些信息到错误模板,通过存储HTTP状态码的 status_code 变量和存储message信息的status_text变量。

你可以通过实现HttpExceptionInterface接口来自定义这个状态码,要填实getStatusCode()方法。否则,status_code将默认为500

在开发环境中展示的异常页可以和错误页一样被自定义。对于标准HTML异常页面创建一个新的exception.html.twig模板,或者为JOSN异常页面创建一个exception.json.twig

在开发过程中测试错误页面 

当你处于开发环境时,symfony会显示大型异常页来替代你新美化的自定义错误页面。此时,你怎么能看到它的样子并调试呢?

好在这个默认ExceptionController允许你在开发过程中预览你的错误页面。

要使用这个特性,你需要像下面这样在routing_dev.yml文件中进行定义:

1
2
3
4
# app/config/routing_dev.yml
_errors:
    resource: "@TwigBundle/Resources/config/routing/errors.xml"
    prefix:   /_error
1
2
3
4
5
6
7
8
9
10
<!-- app/config/routing_dev.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">
 
    <import resource="@TwigBundle/Resources/config/routing/errors.xml"
        prefix="/_error" />
</routes>
1
2
3
4
5
6
7
8
9
10
// app/config/routing_dev.php
use Symfony\Component\Routing\RouteCollection;
 
$collection = new RouteCollection();
$collection->addCollection(
    $loader->import('@TwigBundle/Resources/config/routing/errors.xml')
);
$collection->addPrefix("/_error");
 
return $collection;

如果你使用的是symfony老版本,你可能需要把它添加到routing_dev.yml文件。如果是从头开始,Symfony标准版已经为你准备好了这些。

添加这个路由之后,你可以像这样使用URL:

1
2
http://localhost/app_dev.php/_error/{statusCode}
http://localhost/app_dev.php/_error/{statusCode}.{format}

来预览这个错误 页面,对于HTML格式要给一个状态码(status code),或者同时给出状态码+页面格式(format)。

覆写默认的ExceptionController 

如果你需要多一点灵活性,而不仅仅是覆写模板,你可以修改渲染错误页面的控制器。例如,你可能需要传入一些额外的变量到模板中。

为此,在程序中任意的地方直接创建一个新的控制器,并设置twig.exception_controller配置选项来指向它:

1
2
3
# app/config/config.yml
twig:
    exception_controller:  AppBundle:Exception:showException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:twig="http://symfony.com/schema/dic/twig"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/twig
        http://symfony.com/schema/dic/twig/twig-1.0.xsd">
 
    <twig:config>
        <twig:exception-controller>AppBundle:Exception:showException</twig:exception-controller>
    </twig:config>
</container>
1
2
3
4
5
// app/config/config.php
$container->loadFromExtension('twig', array(
    'exception_controller' => 'AppBundle:Exception:showException',
    // ...
));

被TwigBundle用来当作kernel.exception事件的监听器的ExceptionListener,它们创建的请求将被派遣到你的控制器中。此外,你的控制器还将传入两个参数:

exception
一个FlattenException 实例,创建于当前正被操作的异常中
logger
一个DebugLoggerInterface 接口实例,在某些情况下可能是null

而不是尽己所能地从零创建一个新的异常控制器,当然,你会继承默认的 ExceptionController 。在这个例子中,你可能想要覆写 showAction()findTemplate() 方法中的一个,或者全都覆写。其中的后者锁定了要使用的模板。

如果你继承了ExceptionController, 你可以把它配置成一个服务,利用构造器来传入Twig environment以及debug旗标(flag)。

1
2
3
4
5
# app/config/services.yml
services:
    app.exception_controller:
        class: AppBundle\Controller\CustomExceptionController
        arguments: ['@twig', '%kernel.debug%']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- app/config/services.xml -->
<?xml version="1.0" encoding="utf-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"
>
    <services>
        <service id="app.exception_controller"
            class="AppBundle\Controller\CustomExceptionController"
        >
            <argument type="service" id="twig"/>
            <argument>%kernel.debug%</argument>
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
// app/config/services.php
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
 
$definition = new Definition('AppBundle\Controller\CustomExceptionController', array(
    new Reference('twig'),
    '%kernel.debug%'
));
$container->setDefinition('app.exception_controller', $definition);

然后,配置twig.exception_controller,使用控制器为服务的语法。(如,app.exception_controller:showAction

错误页预览,你以这种方式设置自己的控制器时同样适用。

使用kernel.exception事件 

当一个异常被抛出,HttpKernel类就会捕获它,并且派遣一个kernel.exception事件。这给了你“以不同的方式把异常转换为一个Response对象”的力量。

使用这个事件远比之前解释过的更为强大,但这需要彻头彻尾地理解Symfony内部机制。假设,你的代码抛出了一个特定异常,包含了一个针对你的“application domain”的特殊信息。

kernel.exception 事件编写你自己的事件监听器,允许你近距离观察异常并采取对策。这些对策可能包括,对异常进行记录、将用户重新定向其他页面,或者渲染特殊的错误页。

如果你的监听器调用了GetResponseForExceptionEvent 事件的 setResponse() 方法,propagation将被禁止,响应将发送到客户端。

此方法允许你创建中心化和层次化的错误处理:而不是一次又一次的在不同的控制器捕获(以及处理)相同的异常,你只需一个(或者几个)监听器来应对。

查看 ExceptionListener 类的代码,它是一个此种类型高级监听的真实用例。这个监听负责处理程序中各种与安全相关的异常(如 AccessDeniedException),并且会采取诸如将用户重新定向到登录页、令其退出登录和其他手段等措施。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)