Variables concatenation - twig

After about an year of using Smarty i wanted to try Twig.
I am facing a problem concatenating a string and a variable to build dynamically the titles of the page when i switch the language.
In Smarty, the controller passes to the template the variables:
$title_it and $title_en
For the title of the page i do <title>{$title_{$lang}}</title> (where $lang is a global variable) and i can switch the values of the variables when i change the language.
I am not able to replicate this behaviour in Twig.
I tried the following methods without having success:
{{ title_ ~ {{ lang }} }} (I think Twig sees the variable "title_" doesn't exists.
'title_'~{{ lang }} (This prints 'title_it' and not it's content)
Is there a way to keep this logic and continuing to use this approach or do i have to handle the titles of the pages differenly?
Thanks a lot,
Manuel

The _context variable holds all variables in the current context, so you can do:
{{ _context['title_' ~ lang]|default }}
This is basically the same as using the attribute function:
{{ attribute(_context, 'title_' ~ lang)|default }}
I would personally use the former as it's more concise and in my opinion clearer.
The default filter is needed when the environment option strict_variables is set to true (the default value is false, but I prefer to set it to true to avoid accidental problems caused by e.g. typos), otherwise you'll get a Twig_Error_Runtime exception if the variable doesn't exist. For example, if you have variables title_en and title_it but try to output the variable title_de (which doesn't exist), you get that exception with the message Key "title_de" for array with keys "title_en, title_it, lang" does not exist.
A more verbose way to check the existence of a variable is to use the defined test:
{% if _context['title_' ~ lang] is defined %} ... {% endif %}
With the default filter you can also provide a default value:
{{ _context['title_' ~ lang]|default('Default title') }}
If you omit the default value (i.e. you do |default instead of |default('some value')), the default value will be an empty string.
See TwigFiddle

I think here the solutions which can fix your problems.
Controller code:
return $this->render('myTwig.html.twig', array(
'lang'=>'en',
'title_en'=>'English Title',
'title_it'=>'Italian Title'
));
Twig Code:
{% set myVar='title_'~lang %}
{{ attribute(_context, myVar) }}
This will display "English Title" on your page.
Here _context variable is a magic variable in twig where you can find all parameters which you passed from your controller to this twig.
attribute(_context,myVar)
it displays the value from passed parameters key to value.
Hope this will solve your problems.
Thanks

I would change the controller to pre-calculate the language dependent value for title before passing it to the twig template. If you are unable (unwilling) to do that, then ...
<title>{% if lang == 'it' %}{{ title_it }}{% else %}{{ title_en }}{% endif %}</title>

Related

Convert string into node path

So i am working on a Shopware shop, and i want to read a MediaEntity in Twig. To do so, i am creating a string with the node path (adding the product ID as a variable), which just works fine.
To actually access the MediaEntity, i need to convert this string into a real node path. How do i do that? Or is there maybe another way to create this path?
Here's my code:
{% block component_product_box %}
{{ parent() }}
{% set coverIds = "context.extensions.#{product.coverId}.elements" %}
{{ dump() }}
{% endblock %}
I tried it roughly and something like this should work:
{% set coverIds = _context['extensions'][product.coverId]['elements'] %}
This should solve your problem, I hope.
If you really need to work with a string and "dots" notation, this could be of help:
How to use Twig's attributed function to access nested object properties

Check if a variable is defined AND truthy at once in Twig

In PHP I can do
<?php if ($someVar): ?>
This checks if a variable exists and if its value is different from a zero-like value like 0, null, '', and so on.
Is there any way to do so in Twig or do I need to write my own filter for that? At the moment, I must do
{% if someVar is defined and someVar %}
which is a pain when it comes to more complex templates.
There are (at least) two ways of doing this without extending Twig. A third option is to extend Twig by creating e.g. a Twig function. I would probably choose the first way (using the default filter).
By using the default filter
As #The_Unknown pointed out, you can also use the default filter:
{% if someVar|default(null) %}
You can omit passing the default value to the filter, and even omit the parentheses. Then the value will default to an empty string (which is falsey). I.e. these two are equal and valid:
{% if someVar|default() %}
{% if someVar|default %}
Whichever style you choose (default to null, omit the value or omit the parens), stick to it. Be consistent.
See TwigFiddle for a demonstration that truthy values evaluate to true and falsey values evaluate to false (based on the table below).
By setting strict_variables to false
By setting the environment variable strict_variables to false, you can skip the if someVar is defined part and do just {% if someVar %}. As described in Twig's documentation:
strict_variables boolean
If set to false, Twig will silently ignore invalid variables
(variables and or attributes/methods that do not exist) and replace
them with a null value. When set to true, Twig throws an exception
instead (default to false).
Set the variable to false when creating a Twig_Environment instance:
$twig = new Twig_Environment($loader, ['strict_variables' => false]);
If someVar is undefined, then {% if someVar %} is obviously false. The if tag's documentation page describes the edge case rules for defined variables:
The rules to determine if an expression is true or false are the same
as in PHP; here are the edge cases rules:
Value Boolean evaluation
empty string false
numeric zero false
whitespace-only string true
empty array false
null false
non-empty array true
object true
See TwigFiddle for a demonstration (strict_variables is set to false behind the "More options..." link in the header).
By extending Twig
(Disclaimer: I wrote this approach before #The_Unknown pointed out that the default filter can also be used.)
If the idea of setting strict_variables to false is too general, you can also extend Twig. I'd argue that it's better to set strict_variables to true to avoid accidental errors caused by e.g. typos in variable names, so this approach might be better.
I don't think that you can create a filter to do this, as an undefined variable would still throw an exception. You might be able to create a custom tag, test or extension (see Extending Twig for ways to extend Twig); I'm going to create a custom function as it's probably the simplest approach.
$twig->addFunction(new Twig_Function('istruthy', function($context, $var) {
return array_key_exists($var, $context) && $context[$var];
}, ['needs_context' => true]));
The ['needs_context' => true] part is essential here, as then you will have access to $context, which contains the variables present in the current context. (You can e.g. put var_dump($context) above the return statement to see it yourself.)
If you want istruthy to support checking multiple variables at once, you can do this:
$twig->addFunction(new Twig_Function('istruthy', function($context, ...$vars) {
foreach ($vars as $var) {
if (!array_key_exists($var, $context) || !$context[$var]) {
return false;
}
}
return true;
}, ['needs_context' => true]));
Then in Twig you can do:
{% if istruthy('foo') %} ... {% endif %}
{% if istruthy('foo') or istruthy('bar') %} ... {% endif %}
{# These two are the same: #}
{% if istruthy('foo') and istruthy('bar') and istruthy('baz') %} ... {% endif %}
{% if istruthy('foo', 'bar', 'baz') %} ... {% endif %}
{# Ternary operator can also be used: #}
{{ istruthy('foo') ? 'yep' : 'nope' }}
You might want to check in the istruthy function whether the arguments are strings or something else and then act accordingly. array_key_exists expects the first argument to be either a string or an integer.
The following works well:
{% if someVar ?? false %}

Dynamic variable in Twig, example?

I don't quite understand how the attribute function in Twig works. Can somebody help me with an example?
I have a field in a SQL that is named dynamic. I could be eg "field27", but I don't know the number, the number is saved in radio.id. I would like to do someting like this:
{% for radio in gruppeType.radios %}
<td><!-- value of "field" + radio.id--></td>
{% endfor %}
How can I use field + radio.id as the name of the twig-variable?
You can build the field name with a variable, then use it in the attribute function to access the data within the object/array. As example:
{% set fieldName = "field" ~ radio.id %}
{{ attribute(gruppeType, fieldName) }}
A working example can be seen in this twigfiddle
Hope this helps.

How to write that path in string to be parsed - Twig

I set links in this way in twig:
{% set link = '('~product.price~)'~product.name~'' %}
The problem is how to fill the link
{% set link = '('~product.price~)'~product.name~'' %}
This gives me the error: A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "punctuation" of value "{"
{% set link = '('~product.price~)'~product.name~'' %}
Unexpected token "name" of value "restaurant" ("end of statement block" expected)
can you please help me :(
The link will also have an id argument.
The only thing I can think of is writing a filter which takes te id as argument and replaces the empty link, but I'm not quite sure it will work, and it doesn't seem like a good solution.
You should not use parentheses within {% %} blocks.
Furthermore, unless you have a specific reason for putting the whole markup for the link in a variable, you might want to consider creating links like this:
{{ product.name }}

Does Twig allow assignments in conditions and how?

As per title, does Twig allow assignments in conditional and how? I know that not all people like these assignments but sometimes they can be very helpful.
This:
{% if (name = attribute(mappings, property)) is defined %}
{% else %}
{% set attrs = attrs|merge(['%s="%s"'|format(name, value)]) %}
{% endif %}
...is not working and gives me and error:
An opened parenthesis is not properly closed. Unexpected token
"operator" of value "=" ("punctuation" expected with value ")") in
::tooltips.html.twig at line 29.
Your twig error is because of this line
{% if (name = attribute(mappings, property)) is defined %}
Twig doesn't like assignment-in-condition expressions, and personally neither do I, it's just a lazy shortcut and the potential issues isn't worth saving a few keystrokes.
But I have to now admit that I'm confused by what this is supposed to do. You're attempting to capture the result of attribute(mappings, property) into name but if that fails, only then do you do something using name, but by then name won't have a value unless it's been defined elsewhere in the template.

Resources