I have a twig template with the navbar and all other templates (the pages) include this template. I have a value in it which should be equal to all pages. How to set this value?
I tries something like this in a controller:
public function setNotificationsAction() {
$this->setNotifications();
return $this->render('AcmeMyBundle::navbar.html.twig', array(
'debts' => $this->notifications,
));
}
and then this in the template:
<span class="badge badge-important">
{% render(controller('AcmeMyBundle:DebtsLoansController:setNotifications')) %}
{{ debts }}
</span>
The result I want it like this:
<span class="badge badge-important">
3
</span>
but the number should be different and the controller should tell it.
I also tried to create a function which returns the value and to call it in the way like above.
I also tried this syntax
{{ render(controller('AcmeMyBundle:DebtsLoansController:setNotifications')) }}
but it isn't working, too.
I get the following mistake:
The function "controller" does not exist in AcmeMyBundle::navbar.html.twig at line 6
Do you have any idea how to achive this and not to have to edit each controller and each template :S Thanks very much in advance!
Well, I would suggest creating your own Twig extension. Something around the lines of:
<span class="badge">
{{ acme_notifications() }}
</span>
namespace Acme\DemoBundle\Twig\AcmeDemoExtension
class AcmeDemoExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
'acme_notifications' => new \Twig_Function_Method($this, 'getNotifications');
);
}
public function getNotifications()
{
$notifications = ...;
return $notifications;
}
}
Read more about creating your own Twig extension in the Symfony2 documentation.
You don't need the controller part :
{% render "AcmeBundle:MyController:MyAction" %}
Be aware however, that a render is a completely new request going through the whole Symfony lifecycle and thus can impact performance if you abuse it.
Edit : And as #Wouter J has pointed out : prior to Symfony 2.2 use above notation. After Symfony 2.2 the following has to be used :
{{ render(controller('AcmeArticleBundle:Article:recentArticles', { 'max': 3 })) }}
Related
Is there a standard/preferred way in Symfony to pass common variables to the base template?
There are things that are on every page, like a username in the menu that I obviously don't want to have to remember to add for every controller.
My thought was to create a controller for the template and return the data. But wondering if there is something more built in to handle this.
return $this->render(
'#secured/account/profile.html.twig',
array('userForm' => $form->createView(),
'base' => call_base_layout_controller()
);
{# templates/account/profile.html.twig #}
{% extends '#secured/base.html.twig' %}
{% block body %}
{% endblock %}
I cannot find it in the current version docs but, as far as I know you can still access a lot directly from twig through the app twig variable defined automatically by the framework for every request https://symfony.com/doc/3.4/templating/app_variable.html
For example, you can get the current user's username as follows:
{{ app.user.username }}
app holds user, request, session, environment, and debug so you can put other values needed into the session or environment variables, and fetch/render those values directly in the template.
If the data that you want is related to the authenticated user, I would recommend what Arleigh Hix said
Otherwise you can create a class that extends AbstractExtension and fill it with your logic
Anything on this class can be accessed from all your twig pages
// src/Twig/AppExtension.php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('myGlobalData', [$this, 'myGlobalData']),
];
}
public function myGlobalData()
{
return "what ever you want ( ex: a query resualt..)";
}
}
Than on your twig template just call it like this
{{ myGlobalData() }}
Even if it retuns an array, u can access it.
I recently started using Symfony 4 and I am creating my first website with this wonderful framework right now.
I have a sidebar that should be displayed in about half of my routes and the content of the sidebar should be filled with some data from a database.
Currently I use DI in all this routes and pass the result of the injected repository to the template (which includes my sidebar.html.twig) for the route.
public function chalupaBatman(FancyRepository $repository)
{
$sidebarObjects = $repository->getSidebarObjects();
$this->render('controllername/chalupabatman.html.twig', [
'sidebarObjects' => $sidebarObjects,
]);
}
I am wondering if there is a way to avoid this for every route I define in my controllers.
So far I found this topic on stackoverflow.
The User Mvin described my problem in a perfect way and also provided some solutions.
However there is still no answer to "what is the best practice" part also the topic is from 2017; therefor, the way to solve this may have changed in Symfony 4.
I ended up with a TwigExtension solution. I'll describe how to achieve it and it would be great if you guys could provide some feedback.
Let me know if I produce massive overhead or miss something essential ;-)
Alright, first of all I created a TwigExtension via command-line
php bin/console make:twig-extension AppExtension
And then I modified the class to look like this:
<?php
namespace App\Twig;
use App\Repository\ArticleRepository;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFunctions(): array
{
return [
new TwigFunction('article_sidebar', [$this, 'getArticleSidebar'], ['needs_environment' => true, 'is_safe' => ['html']]),
];
}
public function getArticleSidebar(\Twig_Environment $twig)
{
$articleRepository = $this->container->get(ArticleRepository::class);
$archive = $articleRepository->myAwesomeLogic('omegalul');
return $twig->render('/article/sidebar.html.twig', [
'archive' => $archive,
]);
}
public static function getSubscribedServices()
{
return [
ArticleRepository::class,
];
}
}
In order to activate Lazy Performance so our Repository and the additional Twig_Environment doesn't get instantiated everytime when we use Twig
we implement the ServiceSubscriberInterface and add the getSubscribedServices-method.
Therefor, our Repo and Twig_Environment only gets instantiated when we actually call {{ article_sidebar() }} inside a template.
{# example-template article_base.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="row">
<div class="col-10">
{% block article_body %}{% endblock %}
</div>
<div class="col-2">
{{ article_sidebar() }}
</div>
</div>
{% endblock %}
Now I am able to define my templates for the article-routes like this:
{# example-template /article/show.html.twig #}
{% extends 'article_base.html.twig' %}
{% block article_body %}
{# display the article here #}
{% endblock %}
this might be a stupid question but maybe you can still help me out here.
What I want: I have a Twig template, that has tabs in it (using Bootstrap tab panels) and now I want to render the tab contents using one Action from one Controller but with different parameters.
So basically, this controller action should take some param "type" and then return different results based on this type. The goal is to not have 2 Controllers to render the twig view that do almost the same except extracting different information based on the param "type.
However, I cannot get it to work. It's either that both tabs render the same results since apparently the view is only rendered once OR that I get exceptions in the twig template at time of rendering.
What would be the right way to approach this problem?
Many thanks.
There are multiple ways to achieve this. You can use if/else statements in your Twig template, but you can also set the template in you controller. It's up to you what suits best.
Method 1: custom template
By default, Symfony uses 'foobar.html.twig' on your foobarAction() method, but you can override this in your method:
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)
);
}
(warning: example from How to Embed Controllers in a Template, but the article itself has nothing to do with your question)
You can set a variable (for example $templateName) and change it:
$templateName = 'recent_list.html.twig';
if ($admin) {
$templateName = 'another_template.html.twig';
}
//or using parameters from you Request
if ($type = $request->request->get('type')) {
$templateName = $type . '.html.twig';
}
return $this->render(
$templateName,
array('articles' => $articles)
);
Method 2: using Twig
Controller:
public function foobarAction(Request $request)
{
return [
'type' => $request->request->get('type');
];
}
You Twig template:
{% if type == 'foo' %}
<h1>Foo!</h1>
<p>Hi, welcome you the foo page!</p>
{% elseif type == 'bar' %}
<h1>Bar!</h1>
<p>Hi, you've reached the Bar page.</p>
{% else %}
<h1>Error!</h1>
<p>Type not found.</p>
{% endif %}
Try render manually each view:
if ($type) {
return $this->render('home/template.html.twig', array('data' => $data));
}
return $this->render('home/another.html.twig', array('another_data' => $another_data));
Many thanks for the answers here, but they did not really helped me. Next time I try to add more code examples ;)
My goal was to render from 1 action into 1 template but with different parameters and render those different contents as partials in the parent view.
Example: Given a template called "view.html.twig" that includes 2 times the template "myPartial.html.twig", but first time with param and second time without param. Based on this param different contents should be returned.
My question was now, why apparently only the first action is rendered in Symfony as both my partial views had the same content.
So this is what I did now:
1 Controller, 2 Actions, both call a 3rd action to fetch the data and return the values to the calling action. Then both actions call the render method with the values rendered from the 3rd action.
This is what it looks now:
<div class="tab-content clearfix">
<div class="tab-pane" id="1b">
{% render controller('MyBundle:MyController:list1') %}
</div>
<div class="tab-pane" id="2b">
{% render controller('MyBundle:MyController:list2', {'type' : 1}) %}
</div>
But what I wanted to achieve was to do something like this (which did not work because then both tabs would show the same content):
<div class="tab-content clearfix">
<div class="tab-pane" id="1b">
{% render controller('MyBundle:MyController:list') %}
</div>
<div class="tab-pane" id="2b">
{% render controller('MyBundle:MyController:list', {'type' : 1}) %}
</div>
Which I find confusing since in both times "render" is called, so I would expect that the Controller is called in both times so that also the partial view in the controller is rendered both times. But apparently this was not the case :( The Controller itself looks something like this:
public function list1Action()
{
$twigVars = //do something to build the twig vars without param;
return $this->render('#MyBundle/Test/partial_list.html.twig', array(
'vars' => $twigVars,
));
}
public function list2Action($param)
{
$twigVars = //do something to build the twig vars with param;
return $this->render('#MyBundle/Test/partial_list.html.twig', array(
'vars' => $twigVars,
));
}
While what I wanted was something like this:
public function listAction($param = '')
{
if ($param) {
$twigVars = //do something to return the twig vars with param;
} else {
$twigVars = //do something else to return twig vars without param;
}
return $this->render('#MyBundle/Test/partial_list.html.twig', array(
'vars' => $twigVars,
));
}
In our Symfony2 Application we render reusable blocks with render_esi. We have this kind of templates:
{% for products as product %}
<div class="product">
<h4>{{ product.name }}</h4>
<div class="ratings">
{{ render_esi(controller('AcmeDemoCommunityBundle:Rating:ratingStars', {
objectType: 'product',
objectId: product.id,
readOnly: true
})) }}
</div>
</div>
{% endfor %}
And of cause we use the render_esi also in the detail page of the product.
I would like to differentiate different types of blocks:
Blocks that renders other actions of the same controller.
Blocks that could be used across other parts of the application.
What is the difference?
Blocks that only renders other actions of the same controller as the parent template are most of the times there to modularize one page and make parts cacheable. This blocks are only used one times in the whole application.
Blocks that renders parts like rating stars or comments are kind of independent widgets that provide an specific functionality. The current controller dose not know anything about this widgets. This kind of blocks are mostly used multiple times in an application.
What does that mean for the software design?
It means that we may want to change the way comments and ratings work in the future. May the not get rendered by an ESI anymore in the future because we have outsourced the functionality to an third-party service and only need to include some kind of JavaScript in this place? Or we render them directly?
This is something that has to be decided by the widget and not by the part that include the widget.
So what could I do to improve my design?
You could keep using ESI (because it makes sense for your usecase), but you should change the way of how the modules are included in the Twig files. You should move the logic for this out of the template into an separate Twig Extension in the AcmeDemoCommunityBundle.
namespace Acme\DemoCommunityBundle\Twig;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Acme\DemoCommunityBundle\Rating\RateableInterface;
class CommunityExtension extends \Twig_Extension
{
/**
* #var string
*/
const RATING_ACTION = 'AcmeDemoCommunityBundle:Rating:ratingStars';
/**
* #var FragmentHandler
*/
protected $handler;
public function __construct(FragmentHandler $handler)
{
$this->handler = $handler;
}
public function getFunctions()
{
return array(
'community_rating' => new \Twig_Function_Method($this, 'communityRating', array('is_safe' => array('html'))),
);
}
public function communityRating(RateableInterface $object, $readOnly = false)
{
return $this->handler->render(new ControllerReference(self::RATING_ACTION, array(
'objectType' => $object->getRatingType(),
'objectId' => $object->getId(),
'readOnly' => $readOnly
)), 'esi', $options);
}
public function getName()
{
return 'community';
}
}
services:
acme_community.twig.community:
class: Acme\DemoCommunityBundle\Twig\CommunityExtension
arguments: [ #fragment.handler ]
tags:
- { name: twig.extension }
Now your template should look like this:
{% for products as product %}
<div class="product">
<h4>{{ product.name }}</h4>
<div class="ratings">
{{ community_rating(product, true) }}
</div>
</div>
{% endfor %}
With this design it is easy to use the rating stars in our application, but we also have the flexibility to change the implementation how ratings work in the future without touching the templates where ratings are used.
I'm sharing templates between client and server and would like to output the raw template inside a script tag which is possible with verbatim.
http://twig.sensiolabs.org/doc/tags/verbatim.html
However it would be nicer if this could be applied as a filter to the include but it doesn't seem possible?
I'm new to twig so excuse me if i've missed obvious functionality.
I ran into the same problem, and this page came up in my search results. In the time since this question was answered, the Twig developers added this functionality into the library. I figured I should add some details for future searchers.
The functionality to include raw text (aka for client-side templates using the same syntax as Twig) is accomplished with the source function.
Ie: {{ source('path/to/template.html.twig') }}
http://twig.sensiolabs.org/doc/functions/source.html
I was looking for something like this too because I'm using Twig.js for some client-side templating along with Symfony. I was trying to share templates between the server-side and client-side code, so I needed content to be parsed in some cases and treated as verbatim in others, which proved to be a bit tricky.
I couldn't find anything built into Twig to help with this, but luckily, it's pretty easy to extend Twig to get what you're looking for. I implemented it as a function, but you may be able to do it as a filter too.
services.yml
statsidekick.twig.include_as_template_extension:
class: StatSidekick\AnalysisBundle\Twig\IncludeAsTemplateExtension
tags:
- { name: twig.extension }
IncludeAsTemplateExtension.php
<?php
namespace StatSidekick\AnalysisBundle\Twig;
use Twig_Environment;
use Twig_Extension;
class IncludeAsTemplateExtension extends Twig_Extension {
/**
* Returns a list of global functions to add to the existing list.
*
* #return array An array of global functions
*/
public function getFunctions() {
return array(
new \Twig_SimpleFunction( 'include_as_template', array( $this, 'includeAsTemplate' ), array( 'needs_environment' => true, 'is_safe' => array( 'html' ) ) )
);
}
function includeAsTemplate( Twig_Environment $env, $location, $id ) {
$contents = $env->getLoader()->getSource( $location );
return "<script data-template-id=\"{$id}\" type=\"text/x-twig-template\">{$contents}</script>";
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName() {
return 'include_as_template_extension';
}
}
Usage in Twig file
{{ include_as_template( 'Your:Template:here.html.twig', 'template-id' ) }}
If you have a Twig file like this:
<ul class="message-list">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
The output will be this:
<script data-template-id="template-id" type="text/x-twig-template">
<ul class="message-list">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</script>
The work is derived from Kari Söderholm's answer here. Hope that helps!