Skip to content

Commit

Permalink
bug #25756 [TwigBundle] Register TwigBridge extensions first (fancyweb)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 3.4 branch (closes #25756).

Discussion
----------

[TwigBundle] Register TwigBridge extensions first

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #25610
| License       | MIT
| Doc PR        | -

The only extension that is really needed to display the current exception page is the `CodeExtension` so we could only prepend this one. However, prepending all of them seems safer to me in the long term.

Also I deeply looked into why this problem only appeared in 3.4 and found the reason. Before 3.4 it actually never reaches the `ExceptionController` for this kind of error because it cannot be resolved because it needs a twig instance in its constructor. This instance is directly taken from the container. Before 3.4 when an exception is thrown when you try to get a service from the container, the instance stored in the `$services` array is unset which is not the case in further versions. So in 3.4+, the `ExceptionController` can be resolved because the instance of twig is still in the container even after the initial exception.

It also means these kind of exceptions are displayed with bugs on all versions before 3.4 I guess. Actually it shows the message 2 times : one for the initial exception and the other one when it tries to resolve the `ExceptionController`.

Maybe another solution might be to use a dedicated twig instance with the right settings just for the exception page ?

Commits
-------

c8465ed [TwigBundle] Register TwigBridge extensions first
  • Loading branch information
fabpot committed Feb 7, 2018
2 parents b1bf41a + c8465ed commit abb9132
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 4 deletions.
Expand Up @@ -34,11 +34,22 @@ public function process(ContainerBuilder $container)
// For instance, global variable definitions must be registered
// afterward. If not, the globals from the extensions will never
// be registered.
$calls = $definition->getMethodCalls();
$definition->setMethodCalls(array());
$currentMethodCalls = $definition->getMethodCalls();
$twigBridgeExtensionsMethodCalls = array();
$othersExtensionsMethodCalls = array();
foreach ($container->findTaggedServiceIds('twig.extension', true) as $id => $attributes) {
$definition->addMethodCall('addExtension', array(new Reference($id)));
$methodCall = array('addExtension', array(new Reference($id)));
$extensionClass = $container->getDefinition($id)->getClass();

if (is_string($extensionClass) && 0 === strpos($extensionClass, 'Symfony\Bridge\Twig\Extension')) {
$twigBridgeExtensionsMethodCalls[] = $methodCall;
} else {
$othersExtensionsMethodCalls[] = $methodCall;
}
}

if (!empty($twigBridgeExtensionsMethodCalls) || !empty($othersExtensionsMethodCalls)) {
$definition->setMethodCalls(array_merge($twigBridgeExtensionsMethodCalls, $othersExtensionsMethodCalls, $currentMethodCalls));
}
$definition->setMethodCalls(array_merge($definition->getMethodCalls(), $calls));
}
}
@@ -0,0 +1,89 @@
<?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\DependencyInjection\Compiler;

use PHPUnit\Framework\TestCase;
use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

class TwigEnvironmentPassTest extends TestCase
{
public function testTwigBridgeExtensionsAreRegisteredFirst()
{
$twigDefinition = new Definition('twig');

$containerBuilderMock = $this->getMockBuilder(ContainerBuilder::class)
->setMethods(array('hasDefinition', 'get', 'findTaggedServiceIds', 'getDefinition'))
->getMock();
$containerBuilderMock
->expects($this->once())
->method('hasDefinition')
->with('twig')
->will($this->returnValue(true));
$containerBuilderMock
->expects($this->once())
->method('findTaggedServiceIds')
->with('twig.extension')
->will($this->returnValue(array(
'other_extension' => array(
array()
),
'twig_bridge_extension' => array(
array()
)
)));

$otherExtensionDefinitionMock = $this->getMockBuilder(Definition::class)
->setMethods(array('getClass'))
->getMock();
$otherExtensionDefinitionMock
->expects($this->once())
->method('getClass')
->will($this->returnValue('Foo\\Bar'));

$twigExtensionDefinitionMock = $this->getMockBuilder(Definition::class)
->setMethods(array('getClass'))
->getMock();
$twigExtensionDefinitionMock
->expects($this->once())
->method('getClass')
->will($this->returnValue('Symfony\\Bridge\\Twig\\Extension\\Foo'));

$containerBuilderMock
->expects($this->exactly(3))
->method('getDefinition')
->withConsecutive(array('twig'), array('other_extension'), array('twig_bridge_extension'))
->willReturnOnConsecutiveCalls(
$this->returnValue($twigDefinition),
$this->returnValue($otherExtensionDefinitionMock),
$this->returnValue($twigExtensionDefinitionMock)
);

$twigEnvironmentPass = new TwigEnvironmentPass();
$twigEnvironmentPass->process($containerBuilderMock);

$methodCalls = $twigDefinition->getMethodCalls();
$this->assertCount(2, $methodCalls);

$twigBridgeExtensionReference = $methodCalls[0][1][0];
$this->assertInstanceOf(Reference::class, $twigBridgeExtensionReference);
/* @var Reference $twigBridgeExtensionReference */
$this->assertEquals('twig_bridge_extension', $twigBridgeExtensionReference->__toString());

$otherExtensionReference = $methodCalls[1][1][0];
$this->assertInstanceOf(Reference::class, $otherExtensionReference);
/* @var Reference $otherExtensionReference */
$this->assertEquals('other_extension', $otherExtensionReference->__toString());
}
}

0 comments on commit abb9132

Please sign in to comment.