【发布时间】:2016-04-10 00:10:50
【问题描述】:
我一直在遵循http://symfony.com/doc/current/cookbook/controller/error_pages.html 上的提示,并在 Resources/TwigBundle/views/Exceptions 中创建了新模板 error500.html.twig。
这很好用,但如果用户位于网站的网络或管理部分,我希望有不同的页面。
有没有简单的方法可以做到这一点? 谢谢你,迈克。
【问题讨论】:
我一直在遵循http://symfony.com/doc/current/cookbook/controller/error_pages.html 上的提示,并在 Resources/TwigBundle/views/Exceptions 中创建了新模板 error500.html.twig。
这很好用,但如果用户位于网站的网络或管理部分,我希望有不同的页面。
有没有简单的方法可以做到这一点? 谢谢你,迈克。
【问题讨论】:
我认为最好的方法是overriding the default ExceptionController。只需扩展它,并覆盖 findTemplate 方法。从请求的属性中检查是否设置了_route 或_controller,并对其进行处理。
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseExceptionController;
class ExceptionController extends BaseExceptionController
{
protected function findTemplate(Request $request, $format, $code, $showException)
{
$routeName = $request->attributes->get('_route');
// You can inject these routes in the construct of the controller
// so that you can manage them from the configuration file instead of hardcode them here
$routesAdminSection = ['admin', 'admin_ban', 'admin_list'];
// This is a poor implementation with in_array.
// You can implement more advanced options using regex
// so that if you pass "^admin" you can match all the routes that starts with admin.
// If the route name match, then we want use a different template: admin_error_CODE.FORMAT.twig
// example: admin_error_404.html.twig
if (!$showException && in_array($routeName, $routesAdminSection, true)) {
$template = sprintf('@AppBundle/Exception/admin_error_%s.%s.twig', $code, format);
if ($this->templateExists($template)) {
return $template;
}
// What you want to do if the template doesn't exist?
// Just use a generic HTML template: admin_error.html.twig
$request->setRequestFormat('html');
return sprintf('@AppBundle/Exception/admin_error.html.twig');
}
// Use the default findTemplate method
return parent::findTemplate($request, $format, $code, $showException);
}
}
然后配置twig.exception_controller:
# app/config/services.yml
services:
app.exception_controller:
class: AppBundle\Controller\ExceptionController
arguments: ['@twig', '%kernel.debug%']
# app/config/config.yml
twig:
exception_controller: app.exception_controller:showAction
然后您可以以相同的方式覆盖模板:
执行此操作的更简单方法是在您的路线的defaults 集合中指定网站部分。示例:
# app/config/routing.yml
home:
path: /
defaults:
_controller: AppBundle:Main:index
section: web
blog:
path: /blog/{page}
defaults:
_controller: AppBundle:Main:blog
section: web
dashboard:
path: /admin
defaults:
_controller: AppBundle:Admin:dashboard
section: admin
stats:
path: /admin/stats
defaults:
_controller: AppBundle:Admin:stats
section: admin
然后你的控制器变成这样:
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseExceptionController;
class ExceptionController extends BaseExceptionController
{
protected function findTemplate(Request $request, $format, $code, $showException)
{
$section = $request->attributes->get('section');
$template = sprintf('@AppBundle/Exception/%s_error_%s.%s.twig', $section, $code, format);
if ($this->templateExists($template)) {
return $template;
}
return parent::findTemplate($request, $format, $code, $showException);
}
}
并以与上述相同的方式配置twig.exception_controller。
现在您只需要为每个部分、代码和格式定义一个模板。
【讨论】:
对于 Symfony 5,这就是我所做的,我相信它也适用于 Symfony 6。它不是很精致,可以改进。
我将vendor/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php 复制到我的应用程序src\CustomerErrorRenderer.php。
有以下区别:
public function __construct(
Environment $twig,
RequestStack $requestStack,
HtmlErrorRenderer $fallbackErrorRenderer = null,
$debug = false
) {
if (!\is_bool($debug) && !\is_callable($debug)) {
throw new \TypeError(
sprintf(
'Argument debug passed to "%s()" must be a boolean or a callable, "%s" given.',
__METHOD__,
get_debug_type($debug)
)
);
}
$this->twig = $twig;
$this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer();
$this->debug = $debug;
$this->requestStack = $requestStack;
}
private function findTemplate(int $statusCode, RequestStack $requestStack): ?string
{
$requestUri = $requestStack->getCurrentRequest()
->getRequestUri();
$prefix = '';
if ($this->startsWith($requestUri, '/admin/')) {
$prefix = 'admin/';
}
$template = sprintf('@Twig/Exception/%serror%s.html.twig', $prefix, $statusCode);
if (
$this->twig->getLoader()
->exists($template)
) {
return $template;
}
$template = sprintf('@Twig/Exception/%serror.html.twig', $prefix);
if (
$this->twig->getLoader()
->exists($template)
) {
return $template;
}
return null;
}
private function startsWith(string $string, string $startString): bool
{
$len = strlen($startString);
return (substr($string, 0, $len) === $startString);
}
然后覆盖services.yaml中的error_renderer:
error_renderer:
class: App\Twig\CustomErrorRenderer
arguments:
- '@twig'
- '@request_stack'
- '@twig.error_renderer.html.inner'
- !service
factory: [ 'Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer', 'getAndCleanOutputBuffer' ]
arguments: ['@request_stack']
在 routes/framework.yaml、/admin/_error/404、/admin/_error/500 中测试(复制来自 resource: '@FrameworkBundle/Resources/config/routing/errors.xml' 的参数)
when@dev:
_errors:
path: /_error/{code}.{_format}
controller: 'error_controller::preview'
defaults:
_format: 'html'
requirements:
code: '\d+'
_errors_admin:
path: /admin/_error/{code}.{_format}
controller: 'error_controller::preview'
defaults:
_format: 'html'
requirements:
code: '\d+'
还有 config/packages/security.yaml
security:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js|admin\/_error)/
security: false
在
中创建的模板templates/bundles/TwigBundle/Exception/admin/
templates/bundles/TwigBundle/Exception/admin/error.html.twig
templates/bundles/TwigBundle/Exception/admin/error404.html.twig
【讨论】: