Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,34 @@
That's it. Your new template is now rendered instead. It has the same context
as the existing Contao one would have (`Template->getData()`). :sparkles:

#### Render event
The system dispatches an event directly *before* a twig template gets rendered. It
allows altering the template context or even the actual template that is going to be
rendered:

```yml
# config/services.yaml
services:
App\EventListener\RenderTemplateListener:
tags:
- { name: kernel.event_listener, event: 'Mvo\ContaoTwig\Event\RenderTemplateEvent' }
```

```php
// App\EventListener\RenderTemplateListener

public function __invoke(\Mvo\ContaoTwig\Event\RenderTemplateEvent $event): void {
$context = $event->getContext(); // the template's context
$template = $event->getTemplate(); // the template's name
$contaoTemplate = $event->getContaoTemplate(); // the original Contao template

// …

$event->setTemplate('another-template.html.twig');
$event->setContext(array_merge($context, ['foo' => 'bar']));
}
```

#### Caveats
As Contao uses input encoding, you'll need to deal for already encoded variables
yourself by adding the `|raw` filter. Use with caution and be sure you know what
Expand Down
53 changes: 53 additions & 0 deletions src/Event/RenderTemplateEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

/*
* @author Moritz Vondano
* @license MIT
*/

namespace Mvo\ContaoTwig\Event;

use Contao\Template;

class RenderTemplateEvent
{
private Template $contaoTemplate;

private string $template;

private array $context;

public function __construct(Template $contaoTemplate, string $template, array $context)
{
$this->contaoTemplate = $contaoTemplate;
$this->template = $template;
$this->context = $context;
}

public function getContaoTemplate(): Template
{
return $this->contaoTemplate;
}

public function getTemplate(): string
{
return $this->template;
}

public function setTemplate(string $template): void
{
$this->template = $template;
}

public function getContext(): array
{
return $this->context;
}

public function setContext(array $context): void
{
$this->context = $context;
}
}
14 changes: 13 additions & 1 deletion src/EventListener/RenderingForwarder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
use Contao\CoreBundle\ServiceAnnotation\Hook;
use Contao\Template;
use Contao\TemplateLoader;
use Mvo\ContaoTwig\Event\RenderTemplateEvent;
use Mvo\ContaoTwig\Filesystem\TemplateLocator;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Twig\Environment;
use Webmozart\PathUtil\Path;

Expand All @@ -26,6 +28,7 @@ class RenderingForwarder
private Environment $twig;
private TemplateLocator $templateLocator;
private ContaoFramework $framework;
private EventDispatcher $eventDispatcher;
private string $rootDir;
private string $environment;

Expand All @@ -35,11 +38,12 @@ class RenderingForwarder
/** @var array<string, string> */
private array $templates = [];

public function __construct(Environment $twig, TemplateLocator $templateLocator, ContaoFramework $framework, string $rootDir, string $environment)
public function __construct(Environment $twig, TemplateLocator $templateLocator, ContaoFramework $framework, EventDispatcher $eventDispatcher, string $rootDir, string $environment)
{
$this->twig = $twig;
$this->templateLocator = $templateLocator;
$this->framework = $framework;
$this->eventDispatcher = $eventDispatcher;
$this->rootDir = $rootDir;
$this->environment = $environment;
}
Expand Down Expand Up @@ -117,6 +121,14 @@ public function render(Template $contaoTemplate): string
throw new \InvalidArgumentException("The template's context must contain a value for '".self::TWIG_TEMPLATE."'");
}

// Dispatch an event that allows altering the data
$event = new RenderTemplateEvent($contaoTemplate, $template, $context);

$this->eventDispatcher->dispatch($event);

$template = $event->getTemplate();
$context = $event->getContext();

if (!$this->twig->getLoader()->exists($template)) {
throw new \RuntimeException("Template '$template' wasn't loaded.");
}
Expand Down
59 changes: 58 additions & 1 deletion tests/EventListener/RenderingForwarderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
use Contao\CoreBundle\Framework\ContaoFramework;
use Contao\Template;
use Contao\TemplateLoader;
use Mvo\ContaoTwig\Event\RenderTemplateEvent;
use Mvo\ContaoTwig\EventListener\RenderingForwarder;
use Mvo\ContaoTwig\Filesystem\TemplateLocator;
use Mvo\ContaoTwig\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Twig\Environment;
use Twig\Loader\LoaderInterface;

Expand Down Expand Up @@ -202,6 +204,57 @@ public function testRenderThrowsIfTemplateWasNotLoaded(): void
$renderingForwarder->render($template);
}

public function testDispatchesEventOnRender(): void
{
$twigLoader = $this->createMock(LoaderInterface::class);
$twigLoader
->expects($this->once())
->method('exists')
->with('bar.html.twig')
->willReturn(true);

/** @var Environment&MockObject $twig */
$twig = $this->createMock(Environment::class);
$twig
->expects($this->once())
->method('render')
->with('bar.html.twig', ['a' => 999])
->willReturn('twig content');
$twig
->method('getLoader')
->willReturn($twigLoader);

$eventDispatcher = new EventDispatcher();

$renderingForwarder = $this->getRenderingForwarder(
$twig, $this->mockContaoFramework(), null, '', $eventDispatcher
);

/** @var Template&MockObject $template */
$template = $this->createMock(Template::class);
$template
->method('getData')
->willReturn([
'twig_template' => 'foo.html.twig',
'context' => ['a' => 123],
]);

$eventDispatcher->addListener(
RenderTemplateEvent::class,
function (RenderTemplateEvent $event): void {
$this->assertSame('foo.html.twig', $event->getTemplate());
$this->assertSame(['a' => 123], $event->getContext());

$event->setTemplate('bar.html.twig');
$event->setContext(['a' => 999]);
}
);

$output = $renderingForwarder->render($template);

$this->assertSame('twig content', $output);
}

/**
* @return ContaoFramework&MockObject
*/
Expand Down Expand Up @@ -231,7 +284,8 @@ private function getRenderingForwarder(
Environment $twig = null,
ContaoFramework $framework = null,
TemplateLocator $locator = null,
string $environment = 'prod'
string $environment = 'prod',
EventDispatcher $eventDispatcher = null
): RenderingForwarder {
/** @var Environment&MockObject $twig */
$twig = $twig ?? $this->createMock(Environment::class);
Expand All @@ -241,10 +295,13 @@ private function getRenderingForwarder(
/** @var ContaoFramework&MockObject $framework */
$framework = $framework ?? $this->getFrameworkWithTemplateLoader();

$eventDispatcher = $eventDispatcher ?? new EventDispatcher();

return new RenderingForwarder(
$twig,
$locator,
$framework,
$eventDispatcher,
$this->rootDir,
$environment
);
Expand Down