Twig included template extending parent's block once - php

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.

Related

render option twice in base.html.twig

I got base.html.twig file inside i wanna render two controller
Is that is possible ?
</head>
<body>
{% block header %}{{ render(controller('App\\Controller\\FrontController::front_header')) }}{% endblock %}
{% block content %}{% endblock %}
{% block footer %}{{ render(controller('App\\Controller\\FrontController::front_footer')) }}{% endblock %}
</body>
{% block javascripts %}{% endblock %}
or what way is the best to handle something like that ?
becouse frontcontroller render anothe index.html.twig which will get some stuff to block content and i wanna have this block content between two another controllers.
why not simply use Twig include for your header and your footer?

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

twig: appending to parent template region from multiple includes

general.tpl:
<head>
<!-- some default meta tags -->
</head>
<body>
{% block mainArea %}{% endblock %}
{% block sidebar %}{% endblock %}
<!-- standard footer -->
<script src="base.js"></script>
{% block js %}{% endblock %}
</body>
page1.tpl:
{% extends "base.tpl" %}
{% block mainArea %}
some content
{% endblock %}
{% block sidebar %}
...
{% include 'one-of-many-widgets.tpl' %}
...
{% endblock %}
one-of-many-widgets.tpl
requires specific javascript library that must be referenced before <body> tag and needs to emit specific in-line javascript before that library but after referencing base.js
requres specific meta tags to be added to page head block
requires to append additional legal information to page footer
Q1: How to do that by putting additional script and meta info into one-of-many-widgets.tpl file? Given that there is several included widgets that all contribute to scripts area of page.
Q2: there is also page2.tpl, that also need it's own additional js and extended meta tags, in addition to emitted by includes. Is it possible to combine them but not overwrite?
one-of-many-widgets-1.tpl:
{% block jsPreBodyAdditions %}
{{ alreadyCollected() }} widget script tag or inline js to APPEND
{% endblock %}
{% block metaTags %}
{{ alreadyCollected() }} widget additional semantic meta tags to APPEND
{% endblock %}
one-of-many-widgets-2.tpl:
{% block jsPreBodyAdditions %}
{{ alreadyCollected() }} widget script tag or inline js to APPEND
{% endblock %}
{% block metaTags %}
{{ alreadyCollected() }} widget additional semantic meta tags to APPEND
{% endblock %}
page2.tpl:
{% block jsPreBodyAdditions %}
{{ alreadyCollected() }} page2 script tag or inline js to APPEND
{% endblock %}
{% block metaTags %}
{{ alreadyCollected() }} page2 additional semantic meta tags to APPEND
{% endblock %}

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 %}

Categories