Set Twig layout in controller - symfony

I have multiple subdomains, each with its own layout. Some controllers are shared across subdomains (e.g login), some not. What I'd like to do is to set layout according to domain, so that I would not need to write in each template:
{% if app.request.domain == 'one' %}
{% set layout = '::layout-one.html.twig' %}
{% elseif app.request.domain == 'two' %}
{% set layout = '::layout-two.html.twig' %}
...
{% endif %}
{% extends layout %}
Is it possible to set default layout in controller (or somewhere)? E.g:
class FooController
{
function fooAction()
{
...
$templating = $this->get('templating');
$templating->setLayout($layout);
return $templating->renderResponse($view, $parameters, $response);
}
}

If you have a separate config file for each of the domains, you can put the layout config in there and have it available in twig as a global variable:
config_one.yml
twig:
globals:
base_layout: '::layout-one.html.twig'
Then in twig you can just do:
{% extends base_layout %}

You can set layout variable in your FooController:
class FooController
{
function fooAction()
{
...
return $this->render($template, array(
'layout' => $layout
));
}
}
And then use it in your template:
{% extends layout %}

Related

Override twig variable in included template in Symfony 3

I'm trying to override a variable in the included template.
Can I do this in a Symfony3 & Twig?
My twig template looks like this:
{% set foo = 'bar' %}
{% include 'first.html.twig' %}
{% include 'second.html.twig' %}
// first.html.twig
{{ foo }}
{% set foo = 'second' %}
// second.html.twig
{{ foo }}
I get such a result:
bar bar
But I expect:
bar second
The following Twig code:
{% set a = 42 %}
{{ include("first.twig") }}
Will be compiled to this one:
// line 1
$context["a"] = 42;
// line 2
echo twig_include($this->env, $context, "first.twig");
And twig_include prototype is:
# lib/Twig/Extension/Core.php
function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false)
So variables are passed by copy, not by reference in included templates. Thus your changes in included templates won't be reflected to including templates.
Moreover, since Twig 2.0, you can't call TwigEnvironment::addGlobal once twig runtime is initialized, so you can't glitch using simple extensions.
All in all, you can understand that if you need to update variables cross templates, it means some template contains business logic and Twig isn't build for that. You need to prepare the whole context in controllers.
Alternatively you may call a PHP class method from TWIG. Example of a page-counter needed when generating a pdf.
Custom class :
class PageCounter
{
private $pageNumber = 0;
public function incrementPageCounter()
{
$this->pageNumber ++;
return $this->pageNumber;
}
}
Controller:
....
$twigVariables = [
...
'pageCounter' => new PageCounter()
];
return $this->render('template.html.twig', $twigVariables);
Twig template (object pageCounter available from any included template)
{{ pageCounter.incrementPageCounter() }} / {{totalPages}}
You just need to do the check and override the variable with another variable :))
{% if name is defined %}
{% set foo = name %}
{% else %}
{% set foo = 'bar' %}
{% endif %}
{% include 'first.html.twig' %}
{% include 'second.html.twig' %}
// first.html.twig
{% set name = 'first' %}
// second.html.twig
{% set name = 'second' %}
Why not override your variable with your include tag/function like :
{% include 'first.html.twig' with {'foo': 'second'} %}
or :
{ include('first.html.twig', {foo: 'second'}) }}

twig inheritance and symfony2 controller variables

Im trying my first project using symfony2 + twig. I created basic twig template with defined blocks. It basically looks like this
{% block content %}
some content...
{% endblock %}
{% block footer %}
{{ footer.content}}
{% endblock %}
I want footer to be same for all pages. Footer is loaded from DB and its set in controller. I wanted inherit from template described above to other pages but I have to always set footer in controller otherwise variable is not defined.
My questions is if exists any 'nice' way how to set footer variable for multiple templates inherited from parent template?
The solution : Embedding Controllers
In some cases, you need to do more than include a simple template.
Suppose you have a sidebar in your layout that contains the three most
recent articles. Retrieving the three articles may include querying
the database or performing other heavy logic that can't be done from
within a template.
The solution is to simply embed the result of an entire controller
from your template. First, create a controller that renders a certain
number of recent articles:
Controller
// src/AppBundle/Controller/ArticleController.php
namespace AppBundle\Controller;
// ...
class ArticleController extends Controller
{
public function recentArticlesAction($max = 3)
{
// make a database call or other logic
// to get the "$max" most recent articles
$articles = ...;
return $this->render(
'article/recent_list.html.twig',
array('articles' => $articles)
);
}
}
View
{# app/Resources/views/article/recent_list.html.twig #}
{% for article in articles %}
<a href="/article/{{ article.slug }}">
{{ article.title }}
</a>
{% endfor %}
Layout
{# app/Resources/views/base.html.twig #}
{# ... #}
<div id="sidebar">
{{ render(controller(
'AppBundle:Article:recentArticles',
{ 'max': 3 }
)) }}
</div>
you can do with one of the following, 'write a custom Twig Extension'
<?php
namespace AppBundle\Extension;
class MyTwigExtension extends \Twig_Extension
{
private $em;
private $conn;
public function __construct(\Doctrine\ORM\EntityManager $em) {
$this->em = $em;
$this->conn = $em->getConnection();
}
public function getFunctions()
{
return array(
'users' => new \Twig_Function_Method($this, 'getUsers'),
);
}
public function getUsers()
{
$sql = "SELECT * FROM users ORDER BY accountname";
return $this->conn->fetchAll($sql);
}
public function getName()
{
return 'smproc4_twig_extension';
}
}
Register an Extension as a Service
services:
my.twig.extension:
class: AppBundle\Extension\MyTwigExtension
tags:
- { name: twig.extension }
arguments:
em: "#doctrine.orm.entity_manager"
Using the custom Extension
Hello {{ name }}!
<ul>
{% for user in users() %}
<li>{{ user.accountname }}</li>
{% endfor %}
</ul>
have a look at the
How to Write a custom Twig Extension
Creating an Extension

Twig: Cannot override a block that's defined in a macro

I am importing a macro from a layout file, and inside that macro I define a few twig blocks.
When I extend my 'base' layout file in a new 'view' file, I cannot override the content of the blocks that I defined in the macro.
Here is a very simplified, minimal version of my use-case.
Structure:
base.twig
{% import 'macros' as macros %}
<section>
{{ macros.render_sections() }}
{% block working %}
content1-default
{% endblock %}
</section>
macro.twig
{% macro render_sections() %}
<section>
{% block notworking %}
content2-default
{% endblock %}
</section>
{% endmacro %}
view.twig
{% extends "base" %}
{% block working %}
content1-override
{% endblock %}
{% block notworking %}
content2-override
{% endblock %}
Expected behavior:
I expect to see "content1-override content2-override" in my html.
What actually happens:
I see 'content1-override content2-default'
Is there a way to pass the blocks scope to a macro?
I have tried defining the macro inside the base.twig file, as to rule out the import function, but that didn't help.
and obviously everything else works because I can see the block that does get overriden.
You should learn about Twig's internals:
all Twig files are converted to PHP classes
{% extends %} is the equivalent for the litteral PHP extends (used for inheritance).
{% import %} assign a property of your context to an object of type "the twig file you're importing"
blocks are no more than php methods, and the native php inheritance let you overwrite Twig blocks sweetly.
So, taking this into account, your converted twig code will look like this:
class base {
public function display() {
$context['macros'] = new macros();
$context['macros']->render_sections();
echo '<section>';
echo $this->blockWorking();
echo '</section>';
}
public function blockWorking() {
echo "content1-default";
}
}
class macros {
public function render_sections() {
echo '<section>';
echo $this->blockNotWorking();
echo '</section>';
}
public function blockNotWorking() {
echo "content2-defualt";
}
}
class view extends base {
public function blockWorking() {
echo "content1-override";
}
public function blockNotWorking() {
echo "content2-override";
}
}
$view = new view();
$view->display();
You can clearly see here that the blockNotWorking() method of the view class can never overwrite the macro.

Unable to override KnpMenuBundle template

With ...MyBundle\Resources\views\Menu\knp_menu.html.twig, deleting the </li> has no effect on the rendered menu. (Removing the tag is done to remove the space between inline list elements.) I have followed the advice provided in this answer, including the {% import 'knp_menu.html.twig' as knp_menu %} mentioned toward the bottom of that post. Is this because knp_menu.html.twig already extends knp_menu_base.html.twig? Or what?
layout.html.twig:
...
{{ render(controller('VolVolBundle:Default:userMenu')) }}
...
userMenuAction:
$user = $this->getUser();
$tool = $this->container->get('vol.toolbox');
$type = $tool->getUserType($user);
return $this->render(
'VolVolBundle:Default:userMenu.html.twig', array('type' => $type)
);
userMenu.html.twig
...
{% if type is not null %}
{% set menu = "VolVolBundle:Builder:"~type~"Menu" %}
{{ knp_menu_render(menu) }}
{% endif %}
The answer was found deep in here. All that's required to do a global override of the template is to modify config.yml.
config.yml:
...
knp_menu:
twig: # use "twig: false" to disable the Twig extension and the TwigRenderer
template: VolVolBundle:Menu:knp_menu.html.twig
...

Import a file with constants in Twig?

Is it possible to import a file with a bunch of constants in another twig file?
Such as :
{% set VAR1 = constant("...:var1") %}
{% set VAR2 = constant("...:var2") %}
{% set VAR3 = constant("...:var3") %}
...
And then, in another file use those constants?
I have tried include, import, use and embed.
None of these seem to fit the situation.
By design, you can't import variables from another context, as {% include %} without parameter will pass your current context by copy, not by reference.
This is the exact same thing for {% macro %} with the _context variable as parameter, so you cannot create new variables in the context from inside a macro and then {% import %} your macro file anywhere.
But, you can create variables from an extended file, and use them in the child file.
For example:
a.html.twig
{% set test = 'Hello!' %}
b.html.twig
{% extends 'a.html.twig' %}
{{ test }}
Will display "Hello!" when rendering b.html.twig, or any other files that extends a.html.twig.
If, by design, you can't extends a parent template in your application, you can create a Twig extension and implement the getGlobals() method (doc).
Example (for me in Fuz/TestBundle, replace all namespaces to fit your needs):
The Twig extension :
PHP Fuz/TestBundle/Twig/Extension/AppGlobalsExtension.php
<?php
namespace Fuz\TestBundle\Twig\Extension;
class AppGlobalsExtension extends \Twig_Extension
{
public function getGlobals()
{
return array(
'var1' => \Fuz\TestBundle\Component\Service::SOME_CONSTANT_A,
'var2' => \Fuz\TestBundle\Component\Service::SOME_CONSTANT_B,
'var3' => \Fuz\TestBundle\Component\Service::SOME_CONSTANT_C,
// ...
);
}
public function getName()
{
return 'appglobals';
}
}
The configuration :
YML Fuz/TestBundle/Resources/config/services.yml
parameters:
fuz_tools.twig.appglobals_extension.class: Fuz\TestBundle\Twig\Extension\AppGlobalsExtension
services:
fuz_tools.twig.appglobals:
class: '%fuz_tools.twig.appglobals_extension.class%'
tags:
- { name: twig.extension }
You can now use {{ var1 }}, {{ var2 }} ... anywhere in your application views.
This should do the trick for you (with with), just pass variables:
{% include "ACMETestBundle::test.html.twig" with {var1: VAR1, var2: VAR2, ... } %}

Resources