Symfony2 / FOSUserBundle / Twig: Set variable in template to be accessable in Layout - php

I use the Symfony2 framework combined with the FOSUserBundle. I modified the layout by overwriting some FOSUserBundle templates. This is working very well!
The only problem is that I cannot set variables in my templates that extend templates from the FOSUserBundle that are overwritten by my own templates. Difficult to describe, but I try it:
I am overriding the Registration/register_content.html.twig like this:
{% trans_default_domain 'FOSUserBundle' %}
{% set section='register' %}
<h1>{{ 'Anmeldung'|trans }}</h1>
<form action="{{ path('fos_user_registration_register') }}" {{ form_enctype(form) }} method="POST">
{{ form_widget(form) }}
<div>
<input type="submit" value="{{ 'registration.submit'|trans }}" />
</div>
</form>
(/home/simon/server/html/portal/app/Resources/FOSUserBundle/views/Registration/register_content.html.twig)
The point is that I want to set the section variable for my bundle's layout.html.twig to highlight the current section in the menu. This works fine with my regular templates (that are not overwritten from FOSUserBundle)
I also have overwritten layout.html.twig from FOSUserBundle:
{% extends 'MyBundle::layout.html.twig' %}
{% block content %}
{% block fos_user_content %}{% endblock %}
{% endblock %}
(/home/simon/server/html/portal/app/Resources/FOSUserBundle/views/layout.html.twig)
If I set my variable direclty in that layout file it works. But that's not what I want.
How can I solve the problem?
ADDITIONAL INFORMATION:
My menu looks like this
<ul class="nav nav-pills menu">
{% if section is not defined %}
{% set section = '__dummy_undefined' %}
{% endif %}
<li{% if section == 'home' %} class="active"{% endif %}> {{ 'general.home'|trans }}</li>
<li{% if section == 'search' %} class="active"{% endif %}>{{ 'general.search'|trans }}</li>
<li{% if section == 'events' %} class="active"{% endif %}>{{ 'general.calendar'|trans }}</li>
<li{% if section == 'register' %} class="active"{% endif %}>{{ 'general.register'|trans }}</li>
</ul>
So the variable is defined right before it is used.
The menu is contained in a menu.html.twig file which is included like that:
{% include 'DancePortalFrontendBundle::headline.html.twig' %}
The include comes BEFORE
{% block content %}
This should never be displayed!
{% endblock %}
But even if i do the include after or in the content block, I do not get the section value that I set in the register_content.html.twig
Does somebody know how to solve that?

The problem is that your variable in not part of the current scope that you're trying to access. That means that you're defining your variable inside the following block:
{% block fos_user_content %}{% endblock %}
..it will probably not be available in your menu block somewhere else in your template.
When you're trying to access the variable later it's already gone (out of scope). What you could do is use this handy feature:
Per default, blocks have access to variables from outer scopes (http://twig.sensiolabs.org/doc/tags/extends.html)
Define your variable in the main layout or anywhere where it becomes global with respect to the current template:
{% extends 'MyBundle::layout.html.twig' %}
{% block content %}
{#
I did it here but you can really set it anywhere. The point is:
From now on the variable exists and will only be deleted when the
content block closes.
#}
{% set hightlighted_section = '' %}
{% block fos_user_content %}{% endblock %}
{% endblock %}
I don't know your exact use case but in this example you can set the variable anywhere in your FOSUserBundle content files and use it before the content block ends. If that's not enough, define
it on a higher level. If you say you need it in the menu, try setting it one block over your menu block.
The point is where it is defined.. not where you set it.

Related

Difference between Include, Extends, Use, Macro, Embed in Twig

What is the difference between use and include in Twig?
Documentation:
include
The include statement includes a template and returns the rendered content of that template into the current one:
{% include 'header.html' %}
Body here...
{% include 'footer.html' %}
use
The use statement tells Twig to import the blocks defined in blocks.html into the current template (it's like macros, but for blocks):
blocks.html
{% block sidebar %}{% endblock %}
main.html
{% extends "base.html" %}
{% use "blocks.html" %}
{% block title %}{% endblock %}
{% block content %}{% endblock %}
Possible answer:
I think this should explain the difference:
include is to get all the code from an external file and import it
into your actual file at the right location of the call.
use is completely different as it parses the linked file to find a
particular section of code and then overwrites the blocks with the
same name, in your current file, with the one found in this external
file.
include is like "go find this file and render it with my page here".
use is "parse this other file to find block definitions to use instead
of my owns defined here".
If use command finds nothing matching the task, nothing is displayed
at all from this file.
Question
is the explanation correct? are there any other explanations to this difference?
After months, I am posting an answer for any further reference to this question. I also added some description for extends & import & macro & embed for more clearance:
There are various types of inheritance and code reuse in Twig:
Include
Main Goal: Code Reuse
Use Case: Using header.html.twig & footer.html.twig inside base.html.twig.
header.html.twig
<nav>
<div>Homepage</div>
<div>About</div>
</nav>
footer.html.twig
<footer>
<div>Copyright</div>
</footer>
base.html.twig
{% include 'header.html.twig' %}
<main>{% block main %}{% endblock %}</main>
{% include 'footer.html.twig' %}
Extends
Main Goal: Vertical Reuse
Use Case: Extending base.html.twig inside homepage.html.twig & about.html.twig.
base.html.twig
{% include 'header.html.twig' %}
<main>{% block main %}{% endblock %}</main>
{% include 'footer.html.twig' %}
homepage.html.twig
{% extends 'base.html.twig' %}
{% block main %}
<p>Homepage</p>
{% endblock %}
about.html.twig
{% extends 'base.html.twig' %}
{% block main %}
<p>About page</p>
{% endblock %}
Use
Main Goal: Horizontal Reuse
Use Case: sidebar.html.twig in single.product.html.twig & single.service.html.twig.
sidebar.html.twig
{% block sidebar %}<aside>This is sidebar</aside>{% endblock %}
single.product.html.twig
{% extends 'product.layout.html.twig' %}
{% use 'sidebar.html.twig' %}
{% block main %}<main>Product page</main>{% endblock %}
single.service.html.twig
{% extends 'service.layout.html.twig' %}
{% use 'sidebar.html.twig' %}
{% block main %}<main>Service page</main>{% endblock %}
Notes:
It's like macros, but for blocks.
The use tag only imports a template if it does not extend another template, if it does not define macros, and if the body is empty.
Macro
Main Goal: Reusable Markup with Variables
Use Case: A function which gets some variables and outputs some markup.
form.html.twig
{% macro input(name, value, type) %}
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" }}" />
{% endmacro %}
profile.service.html.twig
{% import "form.html.twig" as form %}
<form action="/login" method="post">
<div>{{ form.input('username') }}</div>
<div>{{ form.input('password') }}</div>
<div>{{ form.input('submit', 'Submit', 'submit') }}</div>
</form>
Embed
Main Goal: Block Overriding
Use Case: Embedding pagination.html.twig in product.table.html.twig & service.table.html.twig.
pagination.html.twig
<div id="pagination">
<div>{% block first %}{% endblock %}</div>
{% for i in (min + 1)..(max - 1) %}
<div>{{ i }}</div>
{% endfor %}
<div>{% block last %}{% endblock %}</div>
</div>
product.table.html.twig
{% set min, max = 1, products.itemPerPage %}
{% embed 'pagination.html.twig' %}
{% block first %}First Product Page{% endblock %}
{% block last %}Last Product Page{% endblock %}
{% endembed %}
service.table.html.twig
{% set min, max = 1, services.itemPerPage %}
{% embed 'pagination.html.twig' %}
{% block first %}First Service Page{% endblock %}
{% block last %}Last Service Page{% endblock %}
{% endembed %}
Please note that embedded file (pagination.html.twig) has access to the current context (min, max variables).
Also you may pass extra variables to the embedded file:
pagination.html.twig
<p>{{ count }} items</p>
<div>
<div>{% block first %}{% endblock %}</div>
{% for i in (min + 1)..(max - 1) %}
<div>{{ i }}</div>
{% endfor %}
<div>{% block last %}{% endblock %}</div>
</div>
product.table.html.twig
{% set min, max = 1, products|length %}
{% embed 'pagination.html.twig' with {'count': products|length } %}
{% block first %}First Product Page{% endblock %}
{% block last %}Last Product Page{% endblock %}
{% endembed %}
Note:
It has functionality of both Use & Include together.
Twig performance is spectacular, so chances are you will never care, but be aware that there is a significant difference between using embeds, includes, or macros.
I set up a little test project to demonstrate it: https://github.com/janklan/twig-benchmark
The readme has all the information you need if you're interested to know more, but the general result is, that for the same task executed in a loop of 100k iterations, the times between individual methods on my computer were as follows (lower is better)
embed: 4253ms
include: 3428ms
macro: 2695ms
By the way, the project I shared via Github up generates separate cache directories for each approach (embed/include/macro). If you want to see how different methods in Twig map to the compiled PHP, I recommend you check it out. If you wish to see the cache without having to run the project, check out a branch called with-cache

Display content if on a certain page

I have an app I have been building in symfony and I am trying to only display content when the user is on a certain page. I have a if/else block (shown below) but the content still displays on the page I do not what it too. Am I doing this the wrong way?
the if else block
{% block nav %}
{% set uri = app.request.uri %}
{% if uri != 'login' %}
{% include "StarnesUserBundle::user-nav.html.twig" %}
{% endif %}
{% endblock %}
so it is supposed to display the user-nav.html.twig on every page that is not the /login page.
Define a variable in the Twig template of the login page:
{% set hide_user_nav = true %}
Add a test for this variable in your layout:
{% if (hide_user_nav is not defined) %}
{% block nav %}
{% include "StarnesUserBundle::user-nav.html.twig" %}
{% endblock %}
{% endif %}
Something like this should do the trick. this is untested, but should give you a general idea.
{% block nav %}
{% if app.request.attributes.get('_route') != 'login' %}
{% include "StarnesUserBundle::user-nav.html.twig" %}
{% endif %}
{% endblock %}
This assumes your /login route name is login, so just replace login with the name of your route
If the login page extends the layout page that contains this block then you could just put an empty
{% block nav %}{% endblock nav %}
that would override the parent nav block.

Overriding blocks within included Twig templates

Is there a generally "good" way to achieve this functionality? I have read about the 'use' tag, which seems like the best option so far, but I still don't like that it wont let me bring in any outside html, only blocks.
I will use the 'include' tag in the example below to demonstrate the intent I'm trying to describe.
#base.html.twig
{% include 'elements/header.html.twig' %}
{% block content %}{% endblock %}
{% include 'elements/footer.html.twig' %}
#header.html.twig
<h1>This is my header</h1>
{% block page_title %} Default Page Title {% endblock %}
#index.html.twig
{% extends 'layouts/base.html.twig' %}
{# I want to be able to do this somehow #}
{% block page_title %} This is my overridden page title {% endblock %}
{% block content %} here is the index page content {% endblock %}
I found a solution. Use the block() function to get the child's block content and pass it to header.html.twig as a variable in the include statement:
#base.html.twig
{% include 'elements/header.html.twig' with {page_title: block('page_title')} %}
{% block content %}{% endblock %}
{% include 'elements/footer.html.twig' %}
#header.html.twig
<h1>This is my header</h1>
{% if page_title is empty %}
Default Page Title
{% else %}
{{ page_title }}
{% endif %}
#index.html.twig
{% extends 'layouts/base.html.twig' %}
{% block page_title %} This is my overridden page title {% endblock %}
{% block content %} here is the index page content {% endblock %}
I found a good and real solution reading the documentation for the embed tag. I'm using Twig 2.0 in Symfony 4.
My structure
#templates/base.html.twig
{% block navbar %}
<nav>
{% block menu %}
{% include '_menu' %}
{% endblock menu %}
</nav>
{% endblock navbar %}
{% block content %}
{# several blocks defined #}
{% block footer '' %}
{% endblock content %}
#templates/_menu.html.twig
<ul>
{% block sub_app_menu_itens %}
<li>Main App Menu Item</li>
{% endblock sub_app_menu_itens %}
<li>Menu item</li>
<li>Another menu item</li>
<li>Yet another menu item</li>
</ul>
With code above, when I create my index.html.twig the only code needed to show de default things is
#templates/index.html.twig
{% extends 'base.html.twig' %}
and override blocks defined.
But, when I need to create another page, that use these skeleton, if I try to override block sub_app_menu_itens in another _partial template included, doesn't work. <li>Main App Menu Item</li> is always showed and never overwritten (code above)
#templates/subapp/index.html.twig
{% extends 'base.html.twig' %}
{% block title %}{{ 'agencia.homepage'|trans }}{% endblock %}
{% block menu %}
{% include 'subapp/_menu.html.twig' %}
{% endblock menu %}
{% block user_content %}Content Here{% endblock %}
#templates/subapp/_menu.html.twig
{% extends '_menu.html.twig' %}
{% block sub_app_menu_itens %}
<li>SubApp Menu Item</li>
{% endblock sub_app_menu_itens %}
<li>SubApp Menu Item</li> is never showed. Tried a lot of things like extends and even conditionals with no luck.
THE Solution
The embed tag solve the child partial templates blocks to be overwritten.
#templates/subapp/index.html.twig
{% block menu %}
{% embed '_menu.html.twig' %}
{% block sub_app_menu_itens %}
{% include 'subapp/_menu.html.twig' %}
{% endblock sub_app_menu_itens %}
{% endembed %}
{% endblock menu %}
And simplifing _partial template to have only the html needed
#templates/subapp/_menu.html.twig
<li>SubApp Menu Item</li>
Now <li>SubApp Menu Item</li> will be displayed on the right place of _menu template.
Thinking in levels, a include works like a sublevel (parsed) and all things are only override on the same level, so include doesn't allow to override in another include. Instead, embed templates are bring to the same level (not parsed) and so you can override things.
It can be done. Here's my solution to the problem by passing a variable to the view.
#layout.twig
{% if sidebar is empty %}
This is the default sidebar text.
{% else %}
{% block sidebar %}{% endblock %}
{% endif %}
{% block content %}{% endblock %}
#index.twig
{% extends "layout.twig" %}
{% block sidebar %}
This is the sidebar. It will override the default text.
{% endblock %}
{% block content %}
This is the content.
{% endblock %}
#index.php (SlimPHP)
$app->render('index.twig', ['sidebar' => true]);
Since I'm using SlimPHP the way I call it is by passing sidebar variable to the the view. This can be extended further by using different variables passed to the view, so you can selected sidebar1, sidebar2 etc.
I got it to work with a very simple hack:
Basically it behaves this way because without a {% extends "foo.html.twig" %} it doesn't understand the blocks and simply renders them in place.
So let's extend... nothing:
{% extends "nothing.html.twig" %}
This nothing is really just 1 block:
# nothing.html.twig
{% block main %}
{% endblock %}
The only thing is that you need to wrap everything in a block, this fake "main" block.
Improvement on #webberig's solution, that fixes blocks that are not overridden in all extending templates (comment by #HerrNentu'):
#base.html.twig
{% if block("page_title") is defined %}
{% include 'elements/header.html.twig' with {page_title: block('page_title')} %}
{% else %}
{% include 'elements/header.html.twig' with {page_title: null} %}
{% endif %}
{% block content %}{% endblock %}
{% include 'elements/footer.html.twig' %}
#header.html.twig
<h1>This is my header</h1>
{% if page_title is empty %}
Default Page Title
{% else %}
{{ page_title }}
{% endif %}
#index.html.twig
{% extends 'layouts/base.html.twig' %}
{% block page_title %} This is my overridden page title {% endblock %}
{% block content %} here is the index page content {% endblock %}

Twig included template extending parent's block once

Is there a way to do this? I have a template which outputs one blog article.
Now, on index page I show 10 articles by including that template in for loop and on show page I only show one.
index:
{% block stylesheets %}
{# some stylesheets here #}
{% endblock %}
{% for article in articles %}
{% include VendorBundle:article.html.twig with { 'article': article } %}
{% endfor %}
show:
{% block stylesheets %}
{# some stylesheets here #}
{% endblock %}
{% include VendorBundle:article.html.twig with { 'article': article } %}
Now is there a way to make article.html.twig add something to {% block stylesheets %} of templates that included it automatically? If it is possible, how do I prevent it from adding that 10 times when using for loop?
I'm trying to make my "fragment" templates (templates used for inclusion) define stylesheets that they use and make them "inject" those into page.
Did you try to use use?
Unfortunately I'm not completely sure if I got the question right but {% use %}wasn't mentioned here.
As I understand the question you've got your article.html.twig and include it in e.g. index.html.twig. Now you want to add something from article.html.twig into index.html.twig? Namely to the {% stylesheets %} block.
If I got how to use {% use %} you could maybe try it like this.
article.html.twig
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('bundles/mybundle/css/article.css') }}" type="text/css" />
{% endblock %}
{% block article %}
{# whatever you do here #}
{% endblock %}
index.html.twig
{% use "VendorBundle:article.html.twig" with stylesheets as article_styles %}
{% block stylesheets %}
{{ block('article_styles') }}
{# other styles here #}
{% endblock %}
{% for article in articles %}
{% include VendorBundle:article.html.twig with { 'article': article } %}
{% endfor %}
I have not the chance to test it but the docu states a few very interesting things and it seems like this could be the way to do it.
Horizontal reuse is an advanced Twig feature that is hardly ever needed in regular templates. It is mainly used by projects that need to make template blocks reusable without using inheritance.
I am rather new to stackoverflow. So please if my answer is completely useless could you just post a comment before down voting and I delete it?
However if it does help and there are just some errors in my example, also inform me and I'll fix it.
You can use a new block (not tested):
{# index.html.twig #}
{% block stylesheets -%}
{% block article_styles '' %}
{%- endblock %}
{% for ... -%}
{% include VendorBundle:template.html.twig with {'article': article} %}
{%- endfor %}
{# template.html.twig #}
{% block article_styles -%}
{{ parent() }}
<link rel=stylesheet href=...>
{%- endblock %}
{# ... #}
Edit: Added {{ parent() }}, this will print every content the block already has.

Twig templates, inheritances and block usage

I've created three templates using Twig. The first one has block A defined in it, the second one extends from the first one, but includes a third template which sets the content of block A.
When loading, through the browser, the url which renders b.html.twig, the content in block A (defined by the 3th template) is not positioned block _A is defined.
Example:
<!-- base.html.twig -->
{% block _css '' %}
{% block _header '' %}
{% block _content '' %}
{% block _footer '' %}
{% block _js '' %}
<!-- layout.html.twig -->
<!-- header and footer are placed in the raight zone -->
{% extends ::base.html.twig %}
{% block _header %}
{% render "MyBundleBundle:Header:header" %}
{% endblock %}
{% block _footer %}
{% render "MyBundleBundle:Footer:footer" %}
{% endblock %}
<!-- my_template.html.twig -->
<!-- content is also placed in the right zone but css and js blocks in the included template are not placed where declared in base.html.twig -->
{% extends MyBundleBundle::layout.html.twig %}
{% block _content %}
SOME_CONTENT
{% include MyBundleBundle::my_included_template.html.twig %}
{% endblock %}
<!-- my_included_template.html.twig -->
{% block _css %}
<link.......................>
{% endblock %}
{% block _js %}
<script..................>
{% endblock %}
MORE CONTENT BELONGING TO THE INCLUDED TEMPLATE
What i expect here is, _css blocks content to appear on top of the page and _js block content at the bottom, but that's not happening. I hope you can see where i'm going wrong, thanks!
The include tag does not work the way you think/hope it does. It simply includes the file into the current context. Which means you can't reference blocks. You can only reference blocks in a template that has the extends tag at the top of the file.
You're going to have to restructure the way you've designed your template to make this work for you. Instead of including my_included_template.html.twig you should possibly just render that template directly in your controller. That way it'll have full access to any inherited blocks.

Categories