Passing twig code into a twig template and rendering it - symfony

So I have a twig template that's basically empty, and the application generates some twig code and passes this as a variable into a twig template, like this:
return $this->render('blank.html.twig', [
'twig' => $this->generateTwig()
]);
blank.html.twig looks like this:
{{ twig }}
But when the template is rendered it just has un-rendered twig code inside, like this:
{% extends 'base.html.twig' %} {% block content %} <h1>{{ 'app-name'|trans }}...
How do you render the injected twig code in this example?
Doing a file_put_contents('blank', $this->generateTwig()) works, but this defeats the purpose of using templates.

You can solve this two ways. Either you pre-render $this->generateTwig()'s output and inject (the HTML) into the blank.html.twig template (untested pseudo):
$template = $this->get('twig')->createTemplate($this->generateTwig());
$twig = $template->render();
return $this->render('blank.html.twig', [
'twig' => $twig,
]);
The downside of this approach is that you have to call {{ twig|raw }} in blank.html.twig, otherwise Twig will escape the HTML. Also: it feels a bit weird to pre-render a twig template before feeding it to Twig (again).
The other approach is that you load the template inside of your blank.html.twig template:
{{ include(template_from_string(twig)) }}
The template_from_string function is part of the StringLoaderExtension.
Docs here: https://twig.symfony.com/doc/3.x/recipes.html#loading-a-template-from-a-string
Edit: Thinking of it, a third approach could be even simpler if blank.html.twig is really just a file that outputs {{ twig }}:
$template = $this->get('twig')->createTemplate($this->generateTwig());
return $template->render();

Related

Should I repack the fetched entity object before passing it to the twig template?

I'm using Symfony 4. I have a fetched object $user that has relationships with other entities. I have setup all the getter so I can get other information from that $user object.
$user = $em->getDoctrine()->getRepository(User::class)->find($id);
$usergroup = $user->getGroup()->getName();
What I'll do is to create a new object for repacking the information I needs from the $user object before passing it to the template.
# controller
$repack_user = new \stdClass();
$repack_user->id = $user->getId();
$user_friends = $user->getFrends();
$friends_gf = [];
foreach($user_friends as $friend) {
$friends_gf[] = $friend->getGirlfriend()->getName();
}
$repack_user->friends_gf = $friends_gf;
return $this->render("home.html.twig", ['user' => $repack_user]);
And then in the template, I unpacked it with similar procedures.
# twig template
{{ user.id }}
{% for gf in user.friends_gf %}
{{ gf }}
{% endfor %}
But since I can also call entity function inside twig, I can skip all the whole repacking in the controller and pass the $user object right into the template.
# skipped repack in controller
# twig template
{{ user.getID() }}
{% for friend in user.getfriends %}
{{ friend.getGirlfriend().getName() }}
{% endfor %}
The first way is kind of redundant because I have to do it twice. The second way is kind of hard to read. So which one is the more reasonable approach? What is the common practice for this?
Common practice is definitely to hand over your entity graph directly to the view.
Why do you think your second example is harder to read?
If you don't want those chained calls in the template you might want to consider adding another getter in your User entity. Something like:
public function getNamesOfFriendsGirlfriends()
{
$gfNames = [];
foreach($this->getFriends() as $friend) {
$gfNames[] = $friend->getGirlfriend()->getName();
}
return $gfNames;
}
And then call that in your template:
{% for gfName in user.namesOfFriendsGirlfriends %}
{{ gfName }}
{% endfor %}
And if you need many of these helpers and don't want to spoil your nice and clean entities you might want to consider wrapping them in a Decorator object before using it in the view layer.

Symfony Twig path() not working when using render

I have a layout that includes some chuck of code form a controller called "Layout"
In the header section I have:
{% block accessinfo %} {% render "/layout/accessinfo" %} {% endblock %}
It works pretty fine, the view file content is:
{% extends '::layout.html.twig' %}
{% block body %}
{% if( is_logged == 0 ) %}
Welcome, access your <a id="accessAccount" title="Access your account">here</a>.
{% else %}
Hi, <b><em> {{ is_logged_user_name }}</em></b>, <a id="doLogout" href="javascript:void;">(Logout)</a>.
<i class="icon-user"></i> Your Account
{% endif %}
{% endblock %}
As one can figure out, path('account/manage') points to the Route named 'account/manage', but it's not returning the fully qualified URL to my project.
It returns:
http://localhost.project/account/manage
where it should be:
http://localhost.project/web/app_dev.php/account/manage
NOTE: I have path() all around my template files and they work like a charm.
IMPORTANT: I found out that when I call REQUEST URI inside the action method:
$this->get('request')->server->get('REQUEST_URI')
PHP will return the URL called by the render, in this case is:
/layout/accessinfo
Perhaps I'm not fully understanding your issue but it seems like you missunderstood the use of the path() and render() functions.
First of all if you like to render a controller and you follow the documentation here you would do it like this...
{{ render(controller('AcmeArticleBundle:Article:recentArticles') }}
{# with some parameters #}
{{ render(controller('AcmeArticleBundle:Article:recentArticles', {
'max': 3
})) }}
This assumes you're using Symfony >= 2.2. This follows the bundle:controller:action pattern, which is called Controller Naming Pattern
For a normal use of the path() function you would always use the name of the route and not a hardcoded URL (as it seems like you're passing in URLs and not route names?)
Let's say your route is called accountmanager, your routing.yml should look like this example
# app/config/routing.yml
accountmanager:
path: /account/manage
defaults: { _controller:YourBundleName:YourControllerName:ControllerAction }
And with that in your routing.yml in twig the use of path() is simply achieved by writing {{ path('accountmanager') }}
See the documentation on this topic. Using the name of the route and not a URL pattern ensures that you're getting to the right page which also includes your environment settings (like app_dev.php for your dev environment)

Extending twig for generate html code

I have to generate something like star rating and I have to generate some html for styling ect.
<div class="star on"><i>*</i></div>
<div class="star on"><i>*</i></div>
<div class="star on"><i>*</i></div>
<div class="star"><i></i></div>
<div class="star"><i></i></div>
I want to render using a twig function passing active stars parameters.
{{ stars(4) }}
Is correct use twig functions for generate html code?
Or maybe should I use {% include ... %}
No need in overengineering for such simple task.
If you generate your array in Controller, then it could look like this:
$stars = array(
true,
true,
true,
false,
false,
);
Then you could render your HTML in Twig:
{% for star in stars %}
<div class="star{{ star ? ' on' }}"<i>{{ star ? '*' }}</i></div>
{% endfor %}
In case if you would like to operate with Twig only, I recommend you to use macro:
{% macro stars(stars, total) %}
{% for item in 1..total %}
{{ item }}<br>
{% if item <= stars %}
<div class="star on"><i>*</i></div>
{% else %}
<div class="star"><i></i></div>
{% endif %}
{% endfor %}
{% endmacro %}
If you've defined your macro in the same template, you should call it via _self, if in another file - just like a function, but not forget to import your file into needed twig. See chapter about macros (linked above).
Following call will produce HTML structure that you described in your question:
{{ _self.stars(3,5) }}
See the Extending Twig section of its docs. According to the table in the first section on that page, using functions for content generation is natural. I create a lot of Twig functions and I suggest you create one to solve your problem.
BTW, your function can render a separate template with HTML code — do not generate the HTML code right in your Twig function's PHP code. To render a separate template from your Twig function, inject the service_container service into it, get the templating service and call the render() method on it:
return $this->container->get('templating')->render($pathToYourCustomTemplate);
Usually, it's best to inject the needed services individually, but if you inject the templating service instead of service_container, you'll get a cyclic dependencies problem. That's why injecting the whole container into Twig extensions is a reasonable exception.

symfony2 - twig - how to render a twig template from inside a twig template

I have a xxx.html.twig file which shows a page, but when I want to refresh the page with different data and just update it with new data, I have a select and a submit button for it.
The thing is that I don't know how do I call an action in the controller which I pass parameters to from my twig and call for new data and then I render the same twig template again with new parameters.
How do I do so?
Here are a few different ways:
{{ render(app.request.baseUrl ~ '/helper/test', {"hostid2": hostid } ) }}
or
{% include 'MyCoreBundle:Helper:test.html.twig' with {"hostid2": hostid } only %}
or
{% render controller("MyCoreBundle:Helper:test", {'hostid2': hostid}) %}
Symfony 2.1:
{% render 'YourBundle:YourController:yourAction' with {'var': value} %}
Symfony 2.6+:
{{ render(controller('YourBundle:YourController:yourAction', {'var': value})) }}
And, of course, read the documentation.
I think some parts are depricated here.
To make the include work in latest Symfony 3.1.10, I solved it like this:
{% extends 'base.html.twig' %}
{% block body %}
{{ include('AppBundle:Default:inner_content.html.twig') }}
{% endblock %}
Note: include() with parentheses.
Then all the variables are included from the parent template. If you like to restrict some variables in the child template, you use with ... only (look over)

SonataAdminBundle custom rendering of text fields in list

I'm using symfony2 and SonataAdminBundle.
I have a simple Entity called Post in which I have content field that is basically html text (from a ckeditor for the record). I need to display in the Post list the content field as raw html, without escaping it.
Hacking base_list_field template like this
{% block field %}{{ value|raw }}{% endblock %}
works, but it's clearly not the proper way.
The solution:
I've defined a custom html type in the config.yml for sonata_doctrine_orm_admin:
sonata_doctrine_orm_admin:
templates:
types:
list:
html: MyBundle:Default:list_html.html.twig
And created the custom list_html.html.twig template in which i do not escape HTML:
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field%}
{{value|raw}}
{% endblock %}
Now in the PostAdmin I can define the behaviour of the field in the configureListFields method:
$listMapper
->add('content', 'html')
I know it's an old post that has an accepted answer, but now you can also use the safe option to tell Symfony not to sanitize the output.
$mapper->add('content', null, [
'safe' => true,
]);

Resources