在 Symfony 應(yīng)用程序中,所有的錯(cuò)誤都會(huì)被認(rèn)為是異常,不論只是一個(gè) 404 Not Found 錯(cuò)誤還是你的代碼中的一些異常引起的重大錯(cuò)誤。
在開發(fā)環(huán)境中,Symfony 緩存所有的異常并且通過一個(gè)擁有很多調(diào)試信息的異常頁來幫助你發(fā)現(xiàn)問題的根源:
http://wiki.jikexueyuan.com/project/symfony-cookbook/images/exceptions-in-dev-environment.png" alt="" />
由于這個(gè)頁面包含了很多敏感的內(nèi)部信息,Symfony 不會(huì)將其在產(chǎn)品環(huán)境中展示。作為替代,它只會(huì)展示一個(gè)普通的簡單的錯(cuò)誤頁:
http://wiki.jikexueyuan.com/project/symfony-cookbook/images/errors-in-prod-environment.png" alt="" />
產(chǎn)品環(huán)境下產(chǎn)生的錯(cuò)誤頁可以按照你的要求進(jìn)行不同方式的定制:
如果你想要改變你的錯(cuò)誤頁的內(nèi)容和風(fēng)格來適應(yīng)你的應(yīng)用程序,那么就重寫默認(rèn)錯(cuò)誤頁模板;
如果你想要引入 Symfony 使用的邏輯來產(chǎn)生錯(cuò)誤頁,那么就重寫默認(rèn)異??刂破?/a>;
當(dāng)加載錯(cuò)誤頁的時(shí)候,內(nèi)部的 ExceptionController 被用來產(chǎn)生一個(gè) Twig 模板給用戶顯示。
這個(gè)控制器使用的是 HTTP 狀態(tài)代碼,請(qǐng)求格式和下列邏輯決定了模板文件名:
找一個(gè)給定的格式和狀態(tài)代碼的模板(就像 error404.json.twig 或者 error500.html.twig);
如果不存在以前的模板,拋棄狀態(tài)代碼尋找給定格式的一般的模板(就像 error.json.twig 或者 error.xml.twig);
為了重寫這些模板,簡單的依靠標(biāo)準(zhǔn)的 Symfony 的重寫 bundle 內(nèi)部的模板的方法:將它們放到 app/Resources/TwigBundle/views/Exception/ 目錄。
典型的工程返回的 HTML 和 JSON 頁面,可能像下面那樣:
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 錯(cuò)誤模板重寫成 HTML 頁,創(chuàng)建一個(gè)位于 app/Resources/TwigBundle/views/Exception/ 的新的 error404.html.twig 模板:
{# app/Resources/TwigBundle/views/Exception/error404.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Page not found</h1>
{# example security usage, see below #}
{% if app.user and 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 通過分別儲(chǔ)存在 HTTP 狀態(tài)代碼和信息中的 status_code 和 status_text 變量向錯(cuò)誤頁傳遞一些信息。
你可以通過執(zhí)行 HttpExceptionInterface 來定制狀態(tài)代碼并且需要 getStatusCode() 方法。除此之外,status_code 將會(huì)默認(rèn)成 500。
在開發(fā)環(huán)境中展示的異常頁可以和錯(cuò)誤頁一樣被自定義。為標(biāo)準(zhǔn)的 HTML 異常頁創(chuàng)建一個(gè)新的 exception.html.twig 模板或者為 JSON 異常頁創(chuàng)建一個(gè) exception.json.twig。
自定義設(shè)計(jì)模板時(shí)的最常見的一個(gè)誤區(qū)就是在錯(cuò)誤模板(或者是其它錯(cuò)誤模板所繼承的模板)中使用 is_granted() 功能。如果你那樣做了,你將會(huì)看到 Symfony 出現(xiàn)異常。
這個(gè)問題的原因就是路由在安全層之前完成了。如果 404 錯(cuò)誤出現(xiàn),安全層不能夠加載并且因此 is_granted() 功能未定義。解決方法就是在使用這個(gè)功能之前添加下列的檢查:
{% if app.user and is_granted('...') %}
{# ... #}
{% endif %}
當(dāng)你在開發(fā)環(huán)境的時(shí)候,Symfony 將會(huì)展示出一個(gè)大大的異常頁而不是你新的自定義錯(cuò)誤頁。所以,你如何看到錯(cuò)誤頁長什么樣子并且調(diào)試它呢?
推薦的解決方法就是使用名為 WebfactoryExceptionsBundle 的第三方 bundle。這個(gè) bundle 提供了一個(gè)特殊的測(cè)試控制器可以允許你以任意的 HTTP 狀態(tài)代碼顯示自定義的錯(cuò)誤頁甚至當(dāng) kernel.debug 設(shè)置為 true 也可以。
默認(rèn)的 ExceptionController 也允許在開發(fā)環(huán)境下預(yù)覽你的錯(cuò)誤頁。
這個(gè)特征是在 Symfony 2.6 中引進(jìn)的,在以前,第三方的 bundle WebfactoryExceptionsBundle 可以起到相同的作用。
使用這一特征,你需要在你的 routing_dev.yml 中進(jìn)行如下定義:
YAML:
# app/config/routing_dev.yml
_errors:
resource: "@TwigBundle/Resources/config/routing/errors.xml"
prefix: /_error
XML:
<!-- 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>
PHP:
// 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 文件中添加這個(gè)。如果你是從 scratch 開始的,那么 Symfony 標(biāo)準(zhǔn)版本已經(jīng)包含這個(gè)了。
添加了這條路徑,你可以像以下那樣使用網(wǎng)址來用給定的 HTML 狀態(tài)代碼或者給定的狀態(tài)代碼和格式預(yù)覽錯(cuò)誤頁:
http://localhost/app_dev.php/_error/{statusCode}
http://localhost/app_dev.php/_error/{statusCode}.{format}
如果你需要更多的,超出僅僅重寫模板的靈活性,那么你可以改變產(chǎn)生錯(cuò)誤頁的控制器。舉例來說,你可能需要向你的模板中傳遞一些附加變量。
為了完成這個(gè),在你的應(yīng)用程序的任意的地方創(chuàng)建一個(gè)新的控制器并且設(shè)置 twig.exception_controller 配置選項(xiàng)來指向它:
YAML:
# app/config/config.yml
twig:
exception_controller: AppBundle:Exception:showException
XML:
<!-- 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>
PHP:
// app/config/config.php
$container->loadFromExtension('twig', array(
'exception_controller' => 'AppBundle:Exception:showException',
// ...
));
TwigBundle 使用的 ExceptionListener 類作為 kernel.exception 事件的監(jiān)聽器創(chuàng)建了會(huì)發(fā)送到你的控制器的請(qǐng)求。除此之外,你的控制器還會(huì)被傳遞兩個(gè)參數(shù):
exception 由被處理的異常所創(chuàng)建的 FlattenException 實(shí)例。
logger 一個(gè)在默認(rèn)環(huán)境下可能為空的 DebugLoggerInterface 實(shí)例。
代替創(chuàng)建你能從 scratch 創(chuàng)建的新的異??刂破?,當(dāng)然,也可以擴(kuò)展默認(rèn)的 ExceptionController。這種情況下,你可能想要重寫 showAction() 和 findTemplate() 方法中的一個(gè)或者都重寫。后一個(gè)方法定位了將要使用的模板。
錯(cuò)誤頁預(yù)覽對(duì)你自己建立控制器也可以使用。
當(dāng)出現(xiàn)異常的時(shí)候,HttpKernel 類就會(huì)抓住它并且發(fā)送 kernel.exception 事件。這給你將異常以不同的方式轉(zhuǎn)換成 Response 的方法。
處理這類事件確實(shí)比以前所介紹的更加有效果,而且需要對(duì) Symfony 的內(nèi)部構(gòu)件有全面的了解。假設(shè)你的代碼出現(xiàn)特殊的異常并且對(duì)于你的應(yīng)用程序的域有特殊意義會(huì)怎樣。
為 kernel.exception 事件編寫你自己的監(jiān)聽器使得你可以近距離觀察異常并且依據(jù)它采取不同的行動(dòng)。這些行為可能包括記錄異常,將用戶重新定向到另一個(gè)頁面或者調(diào)用特定的錯(cuò)誤頁。
如果你的監(jiān)聽器在 GetResponseForExceptionEvent 調(diào)用 setResponse() 事件,傳播將會(huì)被禁止并且將會(huì)向客戶發(fā)出回應(yīng)。
這個(gè)方法使得你可以創(chuàng)建集中化和層次化的錯(cuò)誤處理:而不是一次又一次的在不同的控制器抓?。ㄌ幚恚┫嗤漠惓#阒恍枰粋€(gè)(或者幾個(gè))監(jiān)聽器來處理他們。
參見 ExceptionListener 類的代碼作為一個(gè)先進(jìn)的這個(gè)類型的監(jiān)聽器的真實(shí)案例。這個(gè)監(jiān)聽器處理各種各樣的你的應(yīng)用程序出現(xiàn)的安全相關(guān)的異常(如 AccessDeniedException)并且采取像將用戶重新定向到登錄頁,退出以及其它的方法。