Symfony & Twig: how to get vars in twig by DB data? - twig

One question please.
{{ dump(app.user.slugName) }}
If I do the above snippet in Twig, I get the slugName of the user loged ("my-user-2", i.e.) in the app (SlugName is an atribute of the entity user). Ok & Correct. But... I want to order this action from a var (var from BD data)
I have a variable named option which is set like this:
{% set option = 'app.user.slugName' %}
But when I'm trying output this variable with {{ dump(option)}} it returns app.user.slugName as literal. It does not return my-user-2.
Is there are any way in twig to solve this? It's a function to generate a menu, but some links needs some parameters.

I see what you mean, but Twig can't evaluate expression like that.
To achieve something like that you would need a snippet like this,
{% set value_methods = 'app.user.slugname' %}
{% set option_value = _context %}
{% for method in (value_methods|split('.')) if method != '' %}
{% set option_value = attribute(option_value, (method|replace({'()': '', }))) %}
{% endfor %}
{{ option_value }}
twigfiddle
(edit)
Remember you can create a macro to achieve some reusability for this snippet,
{% import _self as macros %}
{{ macros.evaluate(_context, 'app.user.slugname') }}
{% macro evaluate(context, value_methods) %}
{% set option_value = context %}
{% for method in (value_methods|split('.')) if method != '' %}
{% set option_value = attribute(option_value, (method|replace({'()': '', }))) %}
{% endfor %}
{{ option_value }}
{% endmacro %}

Related

TWIG - include variables in different template

I would like to include the same variables in different templates
vars_catchphrase.twig
{% set catchphrase_size = '' %}
{% if var.tile_catchphrase|length <= 4 %}
{% set catchphrase_size = 'size-lg' %}
{% elseif var.tile_catchphrase|length >= 5 and var.tile_catchphrase|length <= 8 %}
{% set catchphrase_size = 'size-md' %}
{% elseif var.tile_catchphrase|length >= 9 and var.tile_catchphrase|length <= 12 %}
{% set catchphrase_size = 'size-sm' %}
{% elseif var.tile_catchphrase|length >= 13 %}
{% set catchphrase_size = 'size-xs' %}
{% endif %}
I tried to include with this (because the context is sometime different) :
{% include 'vars_catchphrase.twig' with { 'var' : post } %}
When the context is different from post I use another one :
{% include 'vars_catchphrase.twig' with { 'var' : item } %}
example.twig
{% for item in list %}
{% include 'vars_catchphrase.twig' with { 'var' : item } %}
<p class="catchphrase {{ catchphrase_size }}">{{ item.title }}</p>
{% endfor %}
The variable is empty. Can I have some help please ?
Templates you include have their own variable scope, this means variables defined inside this template will not be known out the template. This said, included templates also can't alter the parent's context (by default), this is due to twig passing the context array by value, not by reference.
foo.twig
{% set foo = 'foo' %}
{% include 'bar.twig' %}
{{ foo }}
bar.twig
{% set foo = 'bar' %}
The example above will still output foo
In order to solve your problem, I'd suggest adding a custom filter to twig
<?php
$twig->addFilter(new \Twig\TwigFilter('catchphrase_size', function($value) {
switch(true) {
case strlen($value->tile_catchphrase) >= 13: return 'size-xs';
case strlen($value->tile_catchphrase) >= 9: return 'size-sm';
case strlen($value->tile_catchphrase) >= 5: return 'size-md';
default: return 'size-lg';
}
});
This way you can use the filter where ever,
{% for item in list %}
<p class="catchphrase {{ item|catchphrase_size }}">{{ item.title }}</p>
{% endfor %}

Twig check elements of array are in another array

I'm trying to check in twig if any element of one array are set in other array.
Example:
I have user.roles with ['ROLE_ADMIN','ROLE_MANAGER'] and I have the product.roles with ['ROLE_ADMIN','ROLE_USER'].
I want to check (in Twig) if any user.roles are in product.roles, like:
{{ user.roles[0] is product.roles|keys }}
But with each element of user.roles in the same function.
Does anyone know how?
You could use the filter filter to do this, but guessing it would be better to move this to PHP / TwigExtension
{% if user.roles |filter((role) => role in product.roles) | length > 0 %}
Can do something with the post
{% else %}
Access denied
{% endif %}
demo
Use a for loop:
{% for role in user.roles %}
{% if role in product.roles|keys %}
do something...
{% endif %}
{% endfor %}

Dynamic nav-bar elements - passed from Flask to Jinja - inherited layout template

Environment: Python 3.6, Flask 1.02, Jinja2
Objective:
Create a dynamic menu in layout.html (which is extended by content.html)
yet the url_for of the dynamic element is frequently requires a parameter to be passed
Issue statement:
How can I pass the parameters for url_for in Jinja template when rendering the template?
I feel like I would need the syntax of str().format in Jinja..
I tried to:
1. pass each part as a separate value:
menus = [{'url': 'func_name', 'menu_title': 'title', 'param': 'param_name', 'param_val': 'param_value'}]
return render_template('content1.html', menus=menus]
in jinja I tried to call it like: (I also tried it without the plus and double-quotes)
{{ url_for(func_name), param_name+ "=" + param_val }}
During rendering it gives error of
url_for() takes 1 positional argument but 2 were given
2. tried to use the {% set var_name: passed_variable %}
Built on 1st version of menus defined on server side, I tried to set the variables within Jinja, but also failed.
menus = [{'url': 'func_name', 'menu_title': 'title', 'param': 'param_name', 'param_val': 'param_value'}]
return render_template('content1.html', menus=menus]
Jinja
{% for menu in menus %}
{% set url = menu.get('url') %}
{% set param = menu.get('param') %}
{% set value = menu.get('param_val') %}
{% url_for(url, param + "=" + value %}
Yet it also didn't work.
It feels like if I give a param for the url_for syntax (not a hard-wired string) I cannot add the parameters.
3. tried to pass whole content of url_for as a string:
menus={'url_string': " 'func_name', param_name=param_value"}
yet it fails again as url_for syntacs put the whole between apostrophes, which I wouldn't need at the end.
Some references I scanned through.
Flask context-processor
It could work if I would create another template of each nav-bar for each content page - yet with that move i could simply move the navbar into the content page. However that seems dull. Stack Overflow topic
Thus question:
How can I pass the
param_id=paramval['id']
for the url_for syntax during rendering
{{ url_for('edit_question', param_id=paramval['id']) }}
The code/structure stg like below:
layout.html
<html>
<body>
{% for menu in menus %}
{% for key, value in menu.items() %}
<a href="{{ url_for(value) }}" >
{{ key }}
</a>
{% endfor %}
{% endfor %}
{% block content %}
{% endblock %}
</body>
</html>
content1.html
{% extends 'layout.html' %}
{% block content %}
content
{% endblock %}
content2.html
{% extends 'layout.html' %}
{% block content %}
content
{% endblock %}
app.py
#app.route('/')
def index():
menus = [{'menu_title1': 'menu_func_name1'}]
return render_template('content1.html', menus=menus)
#app.route('/menu_details/<int:menu_nr>')
def show_details_of_menu(menu_nr):
menus = [{'menu_title3': 'menu_func_name3', 'menu_param_name': 'menu_param_value'}
return render_template('content2.html', menus=menus)
sorry for the Wall of text..
sigh.. after hours I just found how to construct the syntax. I hope it will help others!
During rendering:
menus = [{'url': 'func_name', 'menu_title': 'title', 'parameters': {'param1': param1_value}}]
return render_template('context.html', menus=menus]
In Jinja, I adjusted the syntax to manage cases where no parameters are needed:
{% for menu in menus %}
{% if menu.get('parameters').items()|length > 0 %}
<a href="{{ url_for(menu.get('url'), **menu.get('parameters')) }}">
{{ menu.get('menu_title') }}
</a>
{% else %}
<a href="{{ url_for(menu.get('url')) }}">
{{ menu.get('menu_title') }}
</a>
{% endif %}
{% endfor %}

Twig get object names ending with numbers dynamically

I have some objects like address1, address2, address3 ... address10. All these objects have lat and long values.
I know we can get it from the attribute() function of Twig, but what I want in my twig template is to get the main objects
{% for i in 1..10 %}
{% set address = address~i %}
{{ address.lat }}
// or like
{{ attribute(address, 'lat') }}
{% endfor %}
Just simply use :
{% for i in 1..10 %}
{{ attribute(attribute(_context, 'address'~i), 'lat') }}
{% endfor %}

Check if variable is string or array in Twig

Is it possible to check if given variable is string in Twig ?
Expected solution:
messages.en.yml:
hello:
stranger: Hello stranger !
known: Hello %name% !
Twig template:
{% set title='hello.stranger' %}
{% set title=['hello.known',{'%name%' : 'hsz'}] %}
{% if title is string %}
{{ title|trans }}
{% else %}
{{ title[0]|trans(title[1]) }}
{% endif %}
Is it possible to do it this way ? Or maybe you have better solution ?
Can be done with the test iterable, added in twig1.7, as Wouter J stated in the comment :
{# evaluates to true if the users variable is iterable #}
{% if users is iterable %}
{% for user in users %}
Hello {{ user }}!
{% endfor %}
{% else %}
{# users is probably a string #}
Hello {{ users }}!
{% endif %}
Reference : iterable
Ok, I did it with:
{% if title[0] is not defined %}
{{ title|trans }}
{% else %}
{{ title[0]|trans(title[1]) }}
{% endif %}
Ugly, but works.
I found iterable to not be good enough since other objects can also be iterable, and are clearly different than an array.
Therefore adding a new Twig_SimpleTest to check if an item is_array is much more explicit. You can add this to your app configuration / after twig is bootstrapped.
$isArray= new Twig_SimpleTest('array', function ($value) {
return is_array($value);
});
$twig->addTest($isArray);
Usage becomes very clean:
{% if value is array %}
<!-- handle array -->
{% else %}
<!-- handle non-array -->
{% endif % }
There is no way to check it correctly using code from the box.
It's better to create custom TwigExtension and add custom check (or use code from OptionResolver).
So, as the result, for Twig 3, it will be smth like this
class CoreExtension extends AbstractExtension
{
public function getTests(): array
{
return [
new TwigTest('instanceof', [$this, 'instanceof']),
];
}
public function instanceof($value, string $type): bool
{
return ('null' === $type && null === $value)
|| (\function_exists($func = 'is_'.$type) && $func($value))
|| $value instanceof $type;
}
}
Assuming you know for a fact that a value is always either a string or an array:
{% if value is iterable and value is not string %}
...
{% else %}
...
{% endif %}
This worked good enough for me in a project I was working on. I realize you may need another solution.

Resources