Dynamic twig variable names - symfony

Given an array of variables sent to a twig template, such as:
$form = $this->createForm( new ServiceItemType()
, $entity
, array( 'attr'=>
array(
'em' => $this->EM()
,'group' => true
) ) );
I want to capture the variables for easy access in twig. However:
{% for key, value in form.vars.attr %}
{% set key = value %}
{% endfor %}
remaps the key variable in the for loop.
twig objects to:
{% for key, value in form.vars.attr %}
{% set {{key}} = value %}
{% endfor %}
And stack as I am aware never seems to address set. Would anyone who knows, please indicate how to accomplish this variable assignment?

I know this syntax works
{% render "..." with {(key): value} %}
Did you try the following syntax? As of March, Friday 22nd this syntax didn't work so you need to use a work around.
{% set (key) = value %}
An alternative to that would be to include a template and pass and form.vars.attr.
{% include "YourAwesomeBundle:Controller:template.html.twig" with form.vars.attr %}
You can also merge form.vars.attr with another array using the merge function.
{% set vars = {} %}
{% set vars = vars|merge(form.vars.attr) %}
{% include "YourAwesomeBundle:Controller:template.html.twig" with vars %}
Within the included template you will be able to use the variable em and group.

Related

Symfony ChoiceType deep label customization

I want to customize my EntityType's choice labels highly, like creating a table with multiple attributes of my model.
So I want to access my choices class attributes. The choices are my entities of class MyClass through the EntityType. How can I do so in twig?
Currently I do it like this:
1. in my FormClass I json_encode all fields I need in my label
2. in my template I json_decode these information and display according
IN CODE:
1.
$builder
->add('field', EntityType::class, [
'class' => MyClass::class,
'multiple' => false,
'expanded' => true,
],
'choice_label' => function (MyClass $myClass) {
$data = [
'name' => $myClass->getName(),
'description' => $myClass->getDescription(),
];
return json_encode($data);
},
])
2.
{% block my_form_widget %}
...
{# form is my 'field' FormView of the EntityType #}
{% for child in form %}
{# child is a FormView, one choice of my EntityType #}
{# child.vars.data is boolean as its a checkbox #}
{% set data = child.vars.label|json_decode %}
create some complex html here, like tables
...
{% endfor %}
...
{% endblock %}
Working. But is there a better way?
Thanks,
Kim
In a Symfony form (or form field, which is just a form of its own) that is mapped to an entity, you always have access to the underlying data in form.vars.data. So in this case, form.vars.data would either be null or an instance of MyClass.
For ease of use in your template, you might do something like:
{% set my_object = form.field.vars.data %}
{% if my_object %}
{{ my_object.getName() }}
{{ my_object.getDescription() }}
{% endif %}
Thus there is no need to re-encode your entity data for the view layer, since it's always already available.
If you are working with an EntityType and want to access the properties of each choice's entity, they are available as well in the choices array:
{% for choice in form.field.vars.choices %}
{{ choice.data.getName() }}
{{ choice.data.getDescription() }}
{% endfor %}
A nice trick when you're trying to access any form data and aren't sure where to look is to temporarily add a line to your template like:
{{ dump(form.field) }}
This will allow you to look through the available data and see what all is available. Note that it requires the Twig debug extension to be enabled, and XDebug to be enabled in PHP in order to make the output look nice.
Ok I got it, here is an example of how to access the data of your EntityType's choices in twig. You can check the child.parent.vars.choices list.
{% block my_form_widget %}
...
{# form is my 'field' FormView of the EntityType #}
{% for child in form %}
{# child is a FormView, one choice of my EntityType #}
{# child.vars.data is boolean as its a checkbox #}
{% for choice in child.parent.vars.choices if choice.value == child.vars.value %}
{{ choice.data.name }} {# contains MyClass name #}
{{ choice.data.description }} {# contains MyClass description #}
{% endfor %}
...
{% endfor %}
...
{% endblock %}

Looping through values in twig and replacing empty values

The title is a bit ambiguous I know, but let me explain what I'm trying to achieve.
I am attempting to generate a CSV based on data pulled from a doctrine query in my Symfony2 CRM. The data retrieved is based on OpenCart Product and Attribute data, as well as some bespoke information which is irrelevant for this issue.
Each product can have up to 5 different attribute values, named A, B, D, L1 and L2. However, some products do not have all of them, only A, B and L1. The CSV requires each attribute value to be in a separate cell - so the headers are as follows:
ATTRIBUTE: A | ATTRIBUTE: B | ATTRIBUTE: D | ATTRIBUTE: L1 |
ATTRIBUTE: L2
And then I loop through in my Twig file as follows:
{% for attribute in row.product.attributes %}
{% if attribute.text is not null %}
{{ attribute.text }},
{% else %}na,{% endif %}
{% endfor %}
If the product has all 5 attributes, the structure of the CSV is fine. However, if the product only has 3 attributes, it means that all of the subsequent values are pulled back a cell, meaning that the other data is under the wrong headings. I tried checking for values first:
{% for attribute in row.product.attributes %}
{% if attribute.attributeName.name == "A" %}
{% if attribute.text is not null %}
{{ attribute.text }},
{% else %}na,{% endif %}
{% endif %}
{% endfor %}
And I did this for each possible attribute name, but unfortuantely this does not work since if the name does not exist, it just skips it anyway. I'm having trouble trying to think of a way to loop through these attributes and entering a n/a if it's non existent - I'm sure there is a way but I don't know what it is.
For reference, here is the controller code that's generating the data for the CSV:
public function adminCsvAction($filter) {
$repository = $this->getDoctrine()->getRepository('AppBundle:Project');
$stages_repository = $this->getDoctrine()->getRepository('AppBundle:Stage');
$users_repository = $this->getDoctrine()->getRepository('AppBundle:User');
$results = $repository->getSearchResults($filter);
$users = $users_repository->findAll();
$stages = $stages_repository->findBy(array('deleted' => 0), array('sortOrder' => 'ASC'));
$filename = "export_".date("Y_m_d_His").".csv";
$response = $this->render('AppBundle:pages:csv.html.twig', array('data' => $results,'users' => $users, 'stages' => $stages));
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename='.$filename);
return $response;
}
The Project Entity has various mappings, one of which links to the Product table in OpenCart which means all attributes and linked values are accessible via this.
Any help in this is much appreciated.
I also agree with Cerad from comment section - that is not the job for Twig. In case your really need to do it, I would try roughly something like this:
{% set allAttr = ["A","B","D","L1","L2"] %}
{% for attribute in allAttr %}
{% if row.product.attributes[attribute] is defined %}
{{ row.product.attributes[attribute].text }}
{% endif %}
{% if not loop.last %},{% endif %}
{% endfor %}
I guess the is defined is critical here...
OK I figured it out. Using what Jovan Perovic suggested, I came up with this:
{% set allAttr = ["A","B","D","L1","L2"] %}
{% set prodAtts = [] %}
{% for row in data %}
{% set existingAtts = [] %}
{% for att in allAttr %}
{% if att not in prodAtts %}
{% set prodAtts = prodAtts|merge([att]) %}
{% endif %}
{% endfor %}
{% for rowAtt in row.product.attributes %}
{% set existingAtts = existingAtts|merge({(rowAtt.attributeName.name|trim):(rowAtt.attributeName.name|trim~'_'~rowAtt.text|trim)}) %}
{% endfor %}
{% for prodAtt in prodAtts %}
{% if prodAtt not in existingAtts|keys %}
{% set existingAtts = existingAtts|merge({(prodAtt):(prodAtt~'_na')}) %}
{% endif %}
{% endfor %}
{% set orderedAtts = existingAtts|sort %}
....
Then the loop for each row thereafter. I used the attribute name with an underscore in order to be able to sort it correct (as it only sorts by value not key) then used preg_replace to remove it along with any instance of the name so I just ended up with the value.
Bit of a lengthy - and probably over thought - solution but it does work!

Set a Variable with a Twig Macro

I have to set a variable with a macro, that returns a number, so I have this macro:
{% import _self as self %}
{% macro function_size(field) %}
{% import _self as self %}
{# initial Setup #}
{% set field_length = 0 %}
{# Field #}
{% for key, value in field %}
{% if not (key starts with "#") %}
{% set field_length = field_length + 1 %}
{% endif %}
{% endfor %}
{{ field_length }}
{% endmacro %}
The Macro loops through the entries of a field and returns the count of values, that don't start with "#".
So I set a variable with that value:
{% set image_size = self.function_size(content.field_teaser_image) %}
ATTENTION: With this you will set the Variable with Twig Markup. (You can see that when you debug the variable afterwards.)
To get a number/integer as value you have to convert it to a String (that will be interpreted as a number if you calculate with it)
{% set image_size = image_size.__toString %}
With this I set the Variable successfully with a macro.
My Question: Is this a bad practice, are there better ways how to set an Variable?
Thank you!
Two ways to set variables for twig are fine in my opinion.
1. Use theme and other hooks (inside theme or any module) and pass variable from php,
2. create twig extension/filter
Examples:
Filter:
http://leopathu.com/content/create-custom-twig-filter-drupal-8
Extension:
http://symfony.com/doc/current/templating/twig_extension.html

Set Variable only get the last value in a loop in Symfony Twig

I am very new in Symfony and I have a problem in my twig. I just don't know how to do it :(
{% set trans_number = '' %}
{% for tran in trans %}
{% set trans_number = tran.transNumber %}
{{dump(trans_number)}}
// this is what I get when dumping inside the loop: string(10) "1073110793" string(10) "1073145793" string(12) "646721454679"
{% endfor %}
But when I tried to dump it outside the loop:
{% set trans_number = '' %}
{% for tran in trans %}
{% set trans_number = tran.transNumber %}
{% endfor %}
{{dump(trans_number)}}
// I get only the last value string(12) "646721454679"
Now my question is, how can I access all the values assigned to the trans_number outside the loop?
Thanks in advance.
You're correct with the variable scope and handling the scope access by declaring trans_number before the loop, however you're misinterpreting how set works with Twig, or just how variables work.
When you use set trans_number = tran.transNumber, you're just assigning a new value to the trans_number variable each time. When you move the dump outside of the loop, you're no longer dumping the new value of trans_number through each iteration.
If you wish to build a list of values into trans_number, then you need to initialize trans_number as a list and append to it through each iteration of the for loop:
{% set trans_number = [] %}
{% for tran in trans %}
{% set trans_number = trans_number|merge([tran.transNumber]) %}
{% endfor %}
{{dump(trans_number)}}
For people looking for the answer to the question in the title of the question:
You could use the last Filter
Example:
{{ dump( (trans|last) ) }}
Source: http://twig.sensiolabs.org/doc/filters/last.html

Twig / Symfony2 - using a variable inside array merge

{% set var_name1 = "hello" %}
{% set var_name2 = "there" %}
{% array1|merge({var_name1: var_name2}) %}
I was hoping the code above would add this to array1:
hello:there
...but it adds:
var_name1:there
I've tried wrapping {{ }} around var_name1. Is it possible to add a record to an array and use a variable for the key?
Enclose the key name in brackets:
{% array1|merge({(var_name1): var_name2}) %}
Note that if var_name1 is a numeric value, it won't work.
You'll have to concat it with a string value :
{% set array1 = array1|merge({(var_name1~'_'): var_name2}) %}

Resources