Building classic websites with Twig
Roadiz uses the same WebReponse
object and system to render pages using Twig templates. By default, Roadiz will look for a Controller
class in your project src/Controller
directory that matches the requested node-type.
For example, if you have a node-type named Article
, Roadiz will look for a controller named App\Controller\ArticleController
in the src/Controller
directory.
If not, the Roadiz default controller will be used to render the node-type template: RZ\Roadiz\CoreBundle\Controller\DefaultNodeSourceController
. Default controller can be configured in your config/packages/roadiz_core.yaml
file:
# config/packages/roadiz_core.yaml
roadiz_core:
defaultNodeSourceController: 'RZ\Roadiz\CoreBundle\Controller\DefaultNodeSourceController'
Default nodes-sources rendering controller
Here is an example of a default controller that renders a node-source using the WebResponse
object:
<?php
declare(strict_types=1);
namespace RZ\Roadiz\CoreBundle\Controller;
use ApiPlatform\Metadata\IriConverterInterface;
use RZ\Roadiz\CoreBundle\Api\DataTransformer\WebResponseDataTransformerInterface;
use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface;
use RZ\Roadiz\CoreBundle\Entity\NodesSources;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
#[AsController]
final class DefaultNodeSourceController extends AbstractController
{
public function __construct(
private readonly WebResponseDataTransformerInterface $webResponseDataTransformer,
private readonly IriConverterInterface $iriConverter,
) {
}
public function __invoke(Request $request, NodesSources $nodeSource): Response
{
$request->attributes->set('_translation', $nodeSource->getTranslation());
$request->attributes->set('_locale', $nodeSource->getTranslation()->getPreferredLocale());
$iri = $this->iriConverter->getIriFromResource($nodeSource);
$request->attributes->set('_resources', $request->attributes->get('_resources', []) + [$iri => $iri]);
$data = $this->webResponseDataTransformer->transform($nodeSource, WebResponseInterface::class);
return $this->render('@RoadizCore/nodeSource/default.html.twig', [
'webResponse' => $data,
]);
}
}
This controller will be used to render the node-source template located in @RoadizCore/nodeSource/default.html.twig
. You can override this template in your own theme by creating a file at templates/bundles/RoadizCoreBundle/nodeSource/default.html.twig
.
{% extends "base.html.twig" %}
{% block title %}{{ webResponse.item.title }}{% endblock %}
{% macro walk_block(block) %}
<li>{{ block.item.title }}</li>
{% if block.children|length %}
<ul>
{% for child in block.children %}
{{ _self.walk_block(child) }}
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
{% block body %}
{% if webResponse.item %}
<h1><a href="{{ path(webResponse.item) }}">Page: {{ webResponse.item.title }}</a></h1>
{% if webResponse.item.content is defined %}
{{ webResponse.item.content|markdown }}
{% endif %}
{% for image in webResponse.item.images %}
{{ image|display({
'width': 300,
'picture': true,
}) }}
{% endfor %}
{% endif %}
{%- if not webResponse.HidingBlocks and webResponse.blocks|length -%}
<h2>Blocks</h2>
<ul>
{% for block in webResponse.blocks %}
{{ _self.walk_block(block) }}
{% endfor %}
</ul>
{%- endif -%}
{% endblock %}
Creating a controller for a node-type
To create a controller for a specific node-type, you can create a new controller class in your src/Controller
directory. For example, if you want to create a controller for the Article
node-type, you can create a file named ArticleController.php
:
<?php
declare(strict_types=1);
namespace App\Controller;
use ApiPlatform\Metadata\IriConverterInterface;
use RZ\Roadiz\CoreBundle\Api\DataTransformer\WebResponseDataTransformerInterface;
use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface;
use RZ\Roadiz\CoreBundle\Entity\NodesSources;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
#[AsController]
final class ArticleController extends AbstractController
{
public function __construct(
private readonly WebResponseDataTransformerInterface $webResponseDataTransformer,
private readonly IriConverterInterface $iriConverter,
) {
}
public function __invoke(Request $request, NodesSources $nodeSource): Response
{
$request->attributes->set('_translation', $nodeSource->getTranslation());
$request->attributes->set('_locale', $nodeSource->getTranslation()->getPreferredLocale());
$iri = $this->iriConverter->getIriFromResource($nodeSource);
$request->attributes->set('_resources', $request->attributes->get('_resources', []) + [$iri => $iri]);
$data = $this->webResponseDataTransformer->transform($nodeSource, WebResponseInterface::class);
return $this->render('@RoadizCore/nodeSource/article.html.twig', [
'webResponse' => $data,
]);
}
}
Provide globlal variables to Twig templates
You can provide global variables to your Twig templates. For example, you can can add the menus entries to all your templates by creating a Twig extension in your src/Twig
directory:
<?php
declare(strict_types=1);
namespace App\Twig;
use App\TreeWalker\MenuNodeSourceWalker;
use Doctrine\Persistence\ManagerRegistry;
use RZ\Roadiz\CoreBundle\Api\Controller\TranslationAwareControllerTrait;
use RZ\Roadiz\CoreBundle\Api\TreeWalker\TreeWalkerGenerator;
use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Twig\Extension\AbstractExtension;
use Twig\Extension\GlobalsInterface;
final class AppExtension extends AbstractExtension implements GlobalsInterface
{
use TranslationAwareControllerTrait;
public function __construct(
private readonly RequestStack $requestStack,
private readonly ManagerRegistry $managerRegistry,
private readonly PreviewResolverInterface $previewResolver,
private readonly TreeWalkerGenerator $treeWalkerGenerator,
) {
}
#[\Override]
protected function getManagerRegistry(): ManagerRegistry
{
return $this->managerRegistry;
}
#[\Override]
protected function getPreviewResolver(): PreviewResolverInterface
{
return $this->previewResolver;
}
private function getMenus(): array
{
$request = $this->requestStack->getMainRequest();
return $this->treeWalkerGenerator->getTreeWalkersForTypeAtRoot(
'Menu',
MenuNodeSourceWalker::class,
$this->getTranslation($request),
3
);
}
#[\Override]
public function getGlobals(): array
{
return [
'menus' => $this->getMenus(),
];
}
}
Then in your Twig templates, you can access the menus
variable:
{% macro walk_menu(children) %}
{% for walker in children %}
<li>
{% if walker.item.isReachable %}
<a href="{{ path(walker.item) }}">{{ walker.item.title }}</a>
{% else %}
<span>{{ walker.item.title }}</span>
{% endif %}
<ul>
{{ _self.walk_menu(walker.children) }}
</ul>
</li>
{% endfor %}
{% endmacro %}
<nav class="nav-horizontal">
<ul>
{{ _self.walk_menu(menus.mainMenuWalker.children) }}
</ul>
</nav>
<div class="container">{% block body %}{% endblock %}</div>