Skip to content

Commit

Permalink
[TwigBundle] Fix error page preview for custom twig.exception_controller
Browse files Browse the repository at this point in the history
  • Loading branch information
mpdude authored and fabpot committed Nov 21, 2014
1 parent f288a69 commit 2065e00
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 64 deletions.
77 changes: 28 additions & 49 deletions src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php
Expand Up @@ -19,14 +19,19 @@
use Symfony\Component\Templating\TemplateReferenceInterface;

/**
* ExceptionController.
* ExceptionController renders error or exception pages for a given
* FlattenException.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ExceptionController
{
protected $twig;

/**
* @var bool Show error (false) or exception (true) pages by default.
*/
protected $debug;

public function __construct(\Twig_Environment $twig, $debug)
Expand All @@ -38,6 +43,10 @@ public function __construct(\Twig_Environment $twig, $debug)
/**
* Converts an Exception to a Response.
*
* A "showException" request parameter can be used to force display of an error page (when set to false) or
* the exception page (when true). If it is not present, the "debug" value passed into the constructor will
* be used.
*
* @param Request $request The request
* @param FlattenException $exception A FlattenException instance
* @param DebugLoggerInterface $logger A DebugLoggerInterface instance
Expand All @@ -49,25 +58,20 @@ public function __construct(\Twig_Environment $twig, $debug)
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
{
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
$showException = $request->get('showException', $this->debug); // As opposed to an additional parameter, this maintains BC

return $this->createResponse($request, $exception, $this->debug, $logger, $currentContent);
}

/**
* Displays the error page for arbitrary status codes and formats.
*
* @param Request $request The request
* @param int $code The HTTP status code to show the error page for.
*
* @return Response
*
* @throws \InvalidArgumentException When the error template does not exist
*/
public function testErrorPageAction(Request $request, $code)
{
$exception = FlattenException::create(new \Exception("Something has intentionally gone wrong."), $code);
$code = $exception->getStatusCode();

return $this->createResponse($request, $exception, false);
return new Response($this->twig->render(
$this->findTemplate($request, $request->getRequestFormat(), $code, $showException),
array(
'status_code' => $code,
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
'exception' => $exception,
'logger' => $logger,
'currentContent' => $currentContent,
)
));
}

/**
Expand All @@ -90,19 +94,19 @@ protected function getAndCleanOutputBuffering($startObLevel)
* @param Request $request
* @param string $format
* @param int $code An HTTP response status code
* @param bool $debug
* @param bool $showException
*
* @return TemplateReferenceInterface
*/
protected function findTemplate(Request $request, $format, $code, $debug)
protected function findTemplate(Request $request, $format, $code, $showException)
{
$name = $debug ? 'exception' : 'error';
if ($debug && 'html' == $format) {
$name = $showException ? 'exception' : 'error';
if ($showException && 'html' == $format) {
$name = 'exception_full';
}

// when not in debug, try to find a template for the specific HTTP status code and format
if (!$debug) {
// For error pages, try to find a template for the specific HTTP status code and format
if (!$showException) {
$template = new TemplateReference('TwigBundle', 'Exception', $name.$code, $format, 'twig');
if ($this->templateExists($template)) {
return $template;
Expand Down Expand Up @@ -138,29 +142,4 @@ protected function templateExists($template)

return false;
}

/**
* @param Request $request
* @param FlattenException $exception
* @param bool $debug
* @param DebugLoggerInterface $logger
* @param string $currentContent
*
* @return Response
*/
protected function createResponse(Request $request, FlattenException $exception, $debug, DebugLoggerInterface $logger = null, $currentContent = '')
{
$code = $exception->getStatusCode();

return new Response($this->twig->render(
(string) $this->findTemplate($request, $request->getRequestFormat(), $code, $debug),
array(
'status_code' => $code,
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
'exception' => $exception,
'logger' => $logger,
'currentContent' => $currentContent,
)
));
}
}
@@ -0,0 +1,56 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\TwigBundle\Controller;

use Symfony\Component\HttpKernel\Exception\FlattenException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;

/**
* PreviewErrorController can be used to test error pages.
*
* It will create a test exception and forward it to another controller.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class PreviewErrorController
{
protected $kernel;
protected $controller;

public function __construct(HttpKernelInterface $kernel, $controller)
{
$this->kernel = $kernel;
$this->controller = $controller;
}

public function previewErrorPageAction(Request $request, $code)
{
$exception = FlattenException::create(new \Exception("Something has intentionally gone wrong."), $code);

/*
* This Request mimics the parameters set by
* \Symfony\Component\HttpKernel\EventListener\ExceptionListener::duplicateRequest, with
* the additional "showException" flag.
*/

$subRequest = $request->duplicate(null, null, array(
'_controller' => $this->controller,
'exception' => $exception,
'logger' => null,
'format' => $request->getRequestFormat(),
'showException' => false,
));

return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
Expand Up @@ -5,7 +5,7 @@
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="_twig_error_test" path="/{code}.{_format}">
<default key="_controller">twig.controller.exception:testErrorPageAction</default>
<default key="_controller">twig.controller.preview_error:previewErrorPageAction</default>
<default key="_format">html</default>
<requirement key="code">\d+</requirement>
</route>
Expand Down
6 changes: 6 additions & 0 deletions src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml
Expand Up @@ -25,6 +25,7 @@
<parameter key="twig.translation.extractor.class">Symfony\Bridge\Twig\Translation\TwigExtractor</parameter>
<parameter key="twig.exception_listener.class">Symfony\Component\HttpKernel\EventListener\ExceptionListener</parameter>
<parameter key="twig.controller.exception.class">Symfony\Bundle\TwigBundle\Controller\ExceptionController</parameter>
<parameter key="twig.controller.preview_error.class">Symfony\Bundle\TwigBundle\Controller\PreviewErrorController</parameter>
</parameters>

<services>
Expand Down Expand Up @@ -133,5 +134,10 @@
<argument type="service" id="twig" />
<argument>%kernel.debug%</argument>
</service>

<service id="twig.controller.preview_error" class="%twig.controller.preview_error.class%">
<argument type="service" id="http_kernel" />
<argument>%twig.exception_listener.controller%</argument>
</service>
</services>
</container>
Expand Up @@ -13,6 +13,7 @@

use Symfony\Bundle\TwigBundle\Tests\TestCase;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
use Symfony\Component\HttpKernel\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;

class ExceptionControllerTest extends TestCase
Expand Down Expand Up @@ -42,27 +43,22 @@ public function testOnlyClearOwnOutputBuffers()
$controller->showAction($request, $flatten);
}

public function testErrorPagesInDebugMode()
public function testShowActionCanBeForcedToShowErrorPage()
{
$twig = new \Twig_Environment(
new \Twig_Loader_Array(array(
'TwigBundle:Exception:error404.html.twig' => '
{%- if exception is defined and status_text is defined and status_code is defined -%}
OK
{%- else -%}
"exception" variable is missing
{%- endif -%}
',
'TwigBundle:Exception:error404.html.twig' => 'ok',
))
);

$request = Request::create('whatever');
$request = Request::create('whatever', 'GET', array('showException' => false));
$exception = FlattenException::create(new \Exception(), 404);
$controller = new ExceptionController($twig, /* "showException" defaults to --> */ true);

$controller = new ExceptionController($twig, /* "debug" set to --> */ true);
$response = $controller->testErrorPageAction($request, 404);
$response = $controller->showAction($request, $exception, null);

$this->assertEquals(200, $response->getStatusCode()); // successful request
$this->assertEquals('OK', $response->getContent()); // content of the error404.html template
$this->assertEquals('ok', $response->getContent()); // content of the error404.html template
}

public function testFallbackToHtmlIfNoTemplateForRequestedFormat()
Expand All @@ -75,9 +71,10 @@ public function testFallbackToHtmlIfNoTemplateForRequestedFormat()

$request = Request::create('whatever');
$request->setRequestFormat('txt');

$exception = FlattenException::create(new \Exception());
$controller = new ExceptionController($twig, false);
$response = $controller->testErrorPageAction($request, 42);

$response = $controller->showAction($request, $exception);

$this->assertEquals('html', $request->getRequestFormat());
}
Expand Down
@@ -0,0 +1,56 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\TwigBundle\Tests\Controller;

use Symfony\Bundle\TwigBundle\Controller\PreviewErrorController;
use Symfony\Bundle\TwigBundle\Tests\TestCase;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class PreviewErrorControllerTest extends TestCase
{
public function testForwardRequestToConfiguredController()
{
$self = $this;

$request = Request::create('whatever');
$response = new Response("");
$code = 123;
$logicalControllerName = 'foo:bar:baz';

$kernel = $this->getMock('\Symfony\Component\HttpKernel\HttpKernelInterface');
$kernel
->expects($this->once())
->method('handle')
->with(
$this->callback(function (Request $request) use ($self, $logicalControllerName, $code) {

$self->assertEquals($logicalControllerName, $request->attributes->get('_controller'));

$exception = $request->attributes->get('exception');
$self->assertInstanceOf('Symfony\Component\HttpKernel\Exception\FlattenException', $exception);
$self->assertEquals($code, $exception->getStatusCode());

$self->assertFalse($request->attributes->get('showException'));

return true;
}),
$this->equalTo(HttpKernelInterface::SUB_REQUEST)
)
->will($this->returnValue($response));

$controller = new PreviewErrorController($kernel, $logicalControllerName);

$this->assertSame($response, $controller->previewErrorPageAction($request, $code));
}
}

0 comments on commit 2065e00

Please sign in to comment.