merged branch fabpot/webprofiler-refactor (PR #6323)
This PR was merged into the master branch. Commits -------142cffb
fixed unit testsfc444f1
fixed support for Twig loaders when they do not extend Twig_ExistsLoaderInterfacef005649
[WebProfilerBundle] decoupled the bundle from TwigBundle35d63df
removed the dependency on the container for exception handling Discussion ---------- Webprofiler refactor This PR removes two hard dependencies from WebProfilerBundle: * The dependency on the DIC; * The dependency on TwigBundle. It also removes the dependency on the DIC in the exception controller from TwigBundle for more consistency.
This commit is contained in:
commit
548dc64639
@ -4,6 +4,7 @@ CHANGELOG
|
||||
2.2.0
|
||||
-----
|
||||
|
||||
* moved the exception controller to be a service (`twig.controller.exception:showAction` vs `Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController::showAction`)
|
||||
* added support for multiple loaders via the "twig.loader" tag.
|
||||
* added automatic registration of namespaced paths for registered bundles
|
||||
* added support for namespaced paths
|
||||
|
@ -13,9 +13,9 @@ namespace Symfony\Bundle\TwigBundle\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
|
||||
use Symfony\Component\DependencyInjection\ContainerAware;
|
||||
use Symfony\Component\HttpKernel\Exception\FlattenException;
|
||||
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
@ -23,8 +23,17 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ExceptionController extends ContainerAware
|
||||
class ExceptionController
|
||||
{
|
||||
protected $twig;
|
||||
protected $debug;
|
||||
|
||||
public function __construct(\Twig_Environment $twig, $debug)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Exception to a Response.
|
||||
*
|
||||
@ -36,17 +45,16 @@ class ExceptionController extends ContainerAware
|
||||
*
|
||||
* @throws \InvalidArgumentException When the exception template does not exist
|
||||
*/
|
||||
public function showAction(FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html')
|
||||
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html')
|
||||
{
|
||||
$this->container->get('request')->setRequestFormat($format);
|
||||
$request->setRequestFormat($format);
|
||||
|
||||
$currentContent = $this->getAndCleanOutputBuffering();
|
||||
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
|
||||
|
||||
$templating = $this->container->get('templating');
|
||||
$code = $exception->getStatusCode();
|
||||
|
||||
return $templating->renderResponse(
|
||||
$this->findTemplate($templating, $format, $code, $this->container->get('kernel')->isDebug()),
|
||||
return new Response($this->twig->render(
|
||||
$this->findTemplate($request, $format, $code, $this->debug),
|
||||
array(
|
||||
'status_code' => $code,
|
||||
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
|
||||
@ -54,19 +62,17 @@ class ExceptionController extends ContainerAware
|
||||
'logger' => $logger,
|
||||
'currentContent' => $currentContent,
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getAndCleanOutputBuffering()
|
||||
protected function getAndCleanOutputBuffering($startObLevel)
|
||||
{
|
||||
// ob_get_level() never returns 0 on some Windows configurations, so if
|
||||
// the level is the same two times in a row, the loop should be stopped.
|
||||
$previousObLevel = null;
|
||||
$startObLevel = $this->container->get('request')->headers->get('X-Php-Ob-Level', -1);
|
||||
|
||||
$currentContent = '';
|
||||
|
||||
while (($obLevel = ob_get_level()) > $startObLevel && $obLevel !== $previousObLevel) {
|
||||
@ -78,14 +84,14 @@ class ExceptionController extends ContainerAware
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EngineInterface $templating
|
||||
* @param string $format
|
||||
* @param integer $code An HTTP response status code
|
||||
* @param Boolean $debug
|
||||
* @param Request $request
|
||||
* @param string $format
|
||||
* @param integer $code An HTTP response status code
|
||||
* @param Boolean $debug
|
||||
*
|
||||
* @return TemplateReference
|
||||
*/
|
||||
protected function findTemplate($templating, $format, $code, $debug)
|
||||
protected function findTemplate(Request $request, $format, $code, $debug)
|
||||
{
|
||||
$name = $debug ? 'exception' : 'error';
|
||||
if ($debug && 'html' == $format) {
|
||||
@ -95,20 +101,38 @@ class ExceptionController extends ContainerAware
|
||||
// when not in debug, try to find a template for the specific HTTP status code and format
|
||||
if (!$debug) {
|
||||
$template = new TemplateReference('TwigBundle', 'Exception', $name.$code, $format, 'twig');
|
||||
if ($templating->exists($template)) {
|
||||
if ($this->templateExists($template)) {
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
|
||||
// try to find a template for the given format
|
||||
$template = new TemplateReference('TwigBundle', 'Exception', $name, $format, 'twig');
|
||||
if ($templating->exists($template)) {
|
||||
if ($this->templateExists($template)) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
// default to a generic HTML exception
|
||||
$this->container->get('request')->setRequestFormat('html');
|
||||
$request->setRequestFormat('html');
|
||||
|
||||
return new TemplateReference('TwigBundle', 'Exception', $name, 'html', 'twig');
|
||||
}
|
||||
|
||||
// to be removed when the minimum required version of Twig is >= 2.0
|
||||
protected function templateExists($template)
|
||||
{
|
||||
$loader = $this->twig->getLoader();
|
||||
if ($loader instanceof \Twig_ExistsLoaderInterface && $loader->exists($template)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$loader->getSource($template);
|
||||
|
||||
return true;
|
||||
} catch (Twig_Error_Loader $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ class Configuration implements ConfigurationInterface
|
||||
|
||||
$rootNode
|
||||
->children()
|
||||
->scalarNode('exception_controller')->defaultValue('Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController::showAction')->end()
|
||||
->scalarNode('exception_controller')->defaultValue('twig.controller.exception:showAction')->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
<parameter key="twig.form.renderer.class">Symfony\Bridge\Twig\Form\TwigRenderer</parameter>
|
||||
<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>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
@ -111,5 +112,10 @@
|
||||
<argument>%twig.exception_listener.controller%</argument>
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
</service>
|
||||
|
||||
<service id="twig.controller.exception" class="%twig.controller.exception.class%">
|
||||
<argument type="service" id="twig" />
|
||||
<argument>%kernel.debug%</argument>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -12,72 +12,33 @@
|
||||
namespace Symfony\Bundle\TwigBundle\Tests\Controller;
|
||||
|
||||
use Symfony\Bundle\TwigBundle\Tests\TestCase;
|
||||
|
||||
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Scope;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class ExceptionControllerTest extends TestCase
|
||||
{
|
||||
protected $controller;
|
||||
protected $container;
|
||||
protected $flatten;
|
||||
protected $templating;
|
||||
protected $kernel;
|
||||
|
||||
protected function setUp()
|
||||
public function testOnlyClearOwnOutputBuffers()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->flatten = $this->getMock('Symfony\Component\HttpKernel\Exception\FlattenException');
|
||||
$this->flatten
|
||||
$flatten = $this->getMock('Symfony\Component\HttpKernel\Exception\FlattenException');
|
||||
$flatten
|
||||
->expects($this->once())
|
||||
->method('getStatusCode')
|
||||
->will($this->returnValue(404));
|
||||
$this->controller = new ExceptionController();
|
||||
$this->kernel = $this->getMock('Symfony\\Component\\HttpKernel\\KernelInterface');
|
||||
$this->templating = $this->getMockBuilder('Symfony\\Bundle\\TwigBundle\\TwigEngine')
|
||||
$twig = $this->getMockBuilder('\Twig_Environment')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->templating
|
||||
$twig
|
||||
->expects($this->any())
|
||||
->method('renderResponse')
|
||||
->method('render')
|
||||
->will($this->returnValue($this->getMock('Symfony\Component\HttpFoundation\Response')));
|
||||
$this->request = Request::create('/');
|
||||
$this->container = $this->getContainer();
|
||||
}
|
||||
$twig
|
||||
->expects($this->any())
|
||||
->method('getLoader')
|
||||
->will($this->returnValue($this->getMock('\Twig_LoaderInterface')));
|
||||
$request = Request::create('/');
|
||||
$request->headers->set('X-Php-Ob-Level', 1);
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
$this->controller = null;
|
||||
$this->container = null;
|
||||
$this->flatten = null;
|
||||
$this->templating = null;
|
||||
$this->kernel = null;
|
||||
}
|
||||
|
||||
public function testOnlyClearOwnOutputBuffers()
|
||||
{
|
||||
$this->request->headers->set('X-Php-Ob-Level', 1);
|
||||
|
||||
$this->controller->setContainer($this->container);
|
||||
$this->controller->showAction($this->flatten);
|
||||
}
|
||||
|
||||
private function getContainer()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->addScope(new Scope('request'));
|
||||
$container->set('request', $this->request);
|
||||
$container->set('templating', $this->templating);
|
||||
$container->setParameter('kernel.bundles', array());
|
||||
$container->setParameter('kernel.cache_dir', __DIR__);
|
||||
$container->setParameter('kernel.root_dir', __DIR__);
|
||||
$container->set('kernel', $this->kernel);
|
||||
|
||||
return $container;
|
||||
$controller = new ExceptionController($twig, false);
|
||||
$controller->showAction($request, $flatten);
|
||||
}
|
||||
}
|
||||
|
@ -12,27 +12,52 @@
|
||||
namespace Symfony\Bundle\WebProfilerBundle\Controller;
|
||||
|
||||
use Symfony\Component\HttpKernel\Exception\FlattenException;
|
||||
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
|
||||
use Symfony\Component\HttpKernel\Profiler\Profiler;
|
||||
use Symfony\Component\HttpKernel\Debug\ExceptionHandler;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseExceptionController;
|
||||
|
||||
/**
|
||||
* ExceptionController.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ExceptionController extends BaseExceptionController
|
||||
class ExceptionController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function showAction(FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html')
|
||||
protected $twig;
|
||||
protected $debug;
|
||||
protected $profiler;
|
||||
|
||||
public function __construct(Profiler $profiler, \Twig_Environment $twig, $debug)
|
||||
{
|
||||
$template = $this->container->get('kernel')->isDebug() ? 'exception' : 'error';
|
||||
$this->profiler = $profiler;
|
||||
$this->twig = $twig;
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the exception panel for the given token.
|
||||
*
|
||||
* @param string $token The profiler token
|
||||
*
|
||||
* @return Response A Response instance
|
||||
*/
|
||||
public function showAction($token)
|
||||
{
|
||||
$this->profiler->disable();
|
||||
|
||||
$exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException();
|
||||
$template = $this->getTemplate();
|
||||
|
||||
if (!$this->twig->getLoader()->exists($template)) {
|
||||
$handler = new ExceptionHandler();
|
||||
|
||||
return new Response($handler->getContent($exception));
|
||||
}
|
||||
|
||||
$code = $exception->getStatusCode();
|
||||
|
||||
return $this->container->get('templating')->renderResponse(
|
||||
'TwigBundle:Exception:'.$template.'.html.twig',
|
||||
return new Response($this->twig->render(
|
||||
$template,
|
||||
array(
|
||||
'status_code' => $code,
|
||||
'status_text' => Response::$statusTexts[$code],
|
||||
@ -40,6 +65,52 @@ class ExceptionController extends BaseExceptionController
|
||||
'logger' => null,
|
||||
'currentContent' => '',
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the exception panel stylesheet for the given token.
|
||||
*
|
||||
* @param string $token The profiler token
|
||||
*
|
||||
* @return Response A Response instance
|
||||
*/
|
||||
public function cssAction($token)
|
||||
{
|
||||
$this->profiler->disable();
|
||||
|
||||
$exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException();
|
||||
$template = $this->getTemplate();
|
||||
|
||||
if (!$this->templateExists($template)) {
|
||||
$handler = new ExceptionHandler();
|
||||
|
||||
return new Response($handler->getStylesheet($exception));
|
||||
}
|
||||
|
||||
return new Response($this->twig->render('@WebProfiler/Collector/exception.css.twig'));
|
||||
}
|
||||
|
||||
protected function getTemplate()
|
||||
{
|
||||
return '@Twig/Exception/'.($this->debug ? 'exception' : 'error').'.html.twig';
|
||||
}
|
||||
|
||||
// to be removed when the minimum required version of Twig is >= 2.0
|
||||
protected function templateExists($template)
|
||||
{
|
||||
$loader = $this->twig->getLoader();
|
||||
if ($loader instanceof \Twig_ExistsLoaderInterface && $loader->exists($template)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$loader->getSource($template);
|
||||
|
||||
return true;
|
||||
} catch (Twig_Error_Loader $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ class TemplateManager
|
||||
$template = substr($template, 0, -10);
|
||||
}
|
||||
|
||||
if (!$this->twig->getLoader()->exists($template.'.html.twig')) {
|
||||
if (!$this->templateExists($template.'.html.twig')) {
|
||||
throw new \UnexpectedValueException(sprintf('The profiler template "%s.html.twig" for data collector "%s" does not exist.', $template, $name));
|
||||
}
|
||||
|
||||
@ -116,4 +116,22 @@ class TemplateManager
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
// to be removed when the minimum required version of Twig is >= 2.0
|
||||
protected function templateExists($template)
|
||||
{
|
||||
$loader = $this->twig->getLoader();
|
||||
if ($loader instanceof \Twig_ExistsLoaderInterface && $loader->exists($template)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$loader->getSource($template);
|
||||
|
||||
return true;
|
||||
} catch (Twig_Error_Loader $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
<parameters>
|
||||
<parameter key="web_profiler.controller.profiler.class">Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController</parameter>
|
||||
<parameter key="web_profiler.controller.router.class">Symfony\Bundle\WebProfilerBundle\Controller\RouterController</parameter>
|
||||
<parameter key="web_profiler.controller.exception.class">Symfony\Bundle\WebProfilerBundle\Controller\ExceptionController</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
@ -23,5 +24,11 @@
|
||||
<argument type="service" id="twig" />
|
||||
<argument type="service" id="router" on-invalid="null" />
|
||||
</service>
|
||||
|
||||
<service id="web_profiler.controller.exception" class="%web_profiler.controller.exception.class%">
|
||||
<argument type="service" id="profiler" />
|
||||
<argument type="service" id="twig" />
|
||||
<argument>%kernel.debug%</argument>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{% block head %}
|
||||
<style type="text/css">
|
||||
{% include '@WebProfiler/Collector/exception.css.twig' %}
|
||||
{% render 'web_profiler.controller.exception:cssAction' with { 'token': token } %}
|
||||
</style>
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
@ -28,7 +28,7 @@
|
||||
</p>
|
||||
{% else %}
|
||||
<div class="sf-reset">
|
||||
{% render 'WebProfilerBundle:Exception:show' with { 'exception': collector.exception, 'format': 'html' } %}
|
||||
{% render 'web_profiler.controller.exception:showAction' with { 'token': token } %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -80,7 +80,7 @@ class TemplateManagerTest extends TestCase
|
||||
->method('hasCollector')
|
||||
->will($this->returnCallback(array($this, 'profileHasCollectorCallback')));
|
||||
|
||||
$this->assertEquals('FooBundle:Collector:foo.html.twig', $this->templateManager->getName($profile, 'foo'));
|
||||
$this->assertEquals('FooBundle:Collector:foo.html.twig', $this->templateManager->getName($profile, 'foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,7 +89,6 @@ class TemplateManagerTest extends TestCase
|
||||
*/
|
||||
public function testGetTemplates()
|
||||
{
|
||||
|
||||
$profile = $this->mockProfile();
|
||||
$profile->expects($this->any())
|
||||
->method('hasCollector')
|
||||
@ -145,6 +144,10 @@ class TemplateManagerTest extends TestCase
|
||||
->method('loadTemplate')
|
||||
->will($this->returnValue('loadedTemplate'));
|
||||
|
||||
$this->twigEnvironment->expects($this->any())
|
||||
->method('getLoader')
|
||||
->will($this->returnValue($this->getMock('\Twig_LoaderInterface')));
|
||||
|
||||
return $this->twigEnvironment;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user