Twig loop gets mutliplied - symfony

Lets say I have some data called 'brands ' in an array past into a twig template like this:
{ 0:1,1:2,2:3,3:4,4:5,5:6,6:7,7:8}
Second array called 'designs' like this
{120:11,123:22,189:32,300:34,400:53,500:63,688:37,799:28}
o/p excepted
12,24,35,38,59,70,45,36
the below code shows what i have tried and the o/p expected for 8 times, but i am getting 64 times the output like
{% for key ,value in brands %}
{% for key,design in designs %}
// for example i need to add the value and design for 8 times
{% set total = value + design %}
{% endfor %}
{% endfor %}

You are incorrectly nesting your loops, and adding each element of designs to each element of brands. You only need one loop:
{% for key, value in brands %}
{% set total = value + designs[key] %}
{% endfor %}
Don't forget you can't access total unless you define it before starting the loop:
{% set total = 0 %}
{% for key, value in brands %}
{% set total = value + designs[key] %}
{% endfor %}
If you're looking for the sum of both of the arrays into one total with varying numbers of elements for brands and designs:
{% set total = 0 %}
{% for key, value in brands %}
{% set total = total + value %}
{% endfor %}
{% for key, value in designs %}
{% set total = total + value %}
{% endfor %}
If you're looking to add together each corresponding element between designs and brands without depending on matching keys - it's a much more difficult process when trying to do it in Twig, and you're missing what Twig is meant for entirely. You should handle this data in the controller or build a better data model to pass to Twig. For example:
$brands = array(0=>1,1=>2,2=>3,3=>4,4=>5,5=>6,6=>7,7=>8);
$designs = array(120=>11,123=>22,189=>32,300=>34,400=>53,500=>63,688=>37,799=>28);
$brands_designs = array();
foreach ($brands as $key => $brand) {
$design_key = key($designs);
$brands_designs[$key] = array(
'brand' => $brand,
'brand_key' => $key,
'design' => next($designs),
'design_key' => $design_key
);
}
return $this->render('AcmeBundle:Folder:template.html.twig', array('brands_designs' => $brands_designs));
Then in your Twig template:
{% for key, value in brands_designs %}
{% set total = value.brand + value.design %}
{% endfor %}
But if you insist on matching each element together and adding them individually...
{% for brandKey, brand in brands %}
{% set outerLoopIndex = loop.index %}
{% for designKey, design in designs %}
{% if outerLoopIndex == loop.index %}
{% set total = brand + design %}
{# do stuff with total here, like {{ total }} #}
{% endif %}
{% endfor %}
{% endfor %}
The above isn't tested, I'm a little worried about variable scope in there. It actually iterates a total of 64 times with your sample set but only outputs on the diagonal (when the outer index == inner index, so 1 == 1, 2 == 2, etc.)

Related

Symfony/ Twig: Filter-filter a collection

Newbie here. Using Symfony 3.4.
I have a Collection of EntityType formtypes I created with the form builder. I'm trying to deposit them on the page selectively. I'd like to loop through them all several times on my template, spitting some of them out according to specific criteria (for instance, the value of a "HiddenType" subtype).
For instance, let's say I had a collection of Movies, some of which have a "Rating" HiddenType. My task is to spit them out selectively in several separate "Rating" sections (i.e. all the R-Rated movies in a single section).
Edit: But! Ratings are user-imputed; dynamically-generated elsewhere. So I can't just make a finite amount of "sections" ahead of time.
My first thought was
{% for rating in ratings %}
<h1>{{rating}}-Rated Movies</h1>
{% for movie in form.movies %}
{% if movie.rating = "R" %}
{{movie}} // put movie formtype/ collection member here somehow...?
{% endif %}
{% endfor %}
{% endfor %}
But I also read that twig has a "filter" filter:
{% for rating in ratings %}
<h1>{{rating}}-Rated Movies</h1>
{% for movie in movies|filter(m=> m.rating == rating) -%}
{{ m }} //put movie formtype/ collection member here somehow...?
{% endfor %}
{% endfor %}
Either way, though, I can't really reconcile the functions I'm familiar with for converting forms to actual html (e.g. form_row() ) with the either of these methods.
Any ideas?
You need to order the movies in your Form by rating. Then in your template do something like this:
{% set rating = null %}
{% for movieForm in form.movies %}
{% set movie = movieForm.vars.value %} <= check if this is the movie entity by dumping it
{% if movie.rating != rating %}
<h1>{{ movie.rating }}</strong>
{% set rating = movie.rating %}
{% endif %}
{{ form_row(movieForm) }}
{% endfor %}

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!

Multiple Collections in Shopify

I have 3 collections displayed on my page right now.
I want to have one pagination at the very bottom that will load the next 50 products for all 3 collections if there are any.
How can I do that?
this is my code:
{% paginate collections.mycollection1.products by settings.pagination_limit %}
<div style="clear:both;">
<h1>Title</h1>
{% assign products_per_row = "4" %}
{% assign limit = 50 %}
{% assign products = collections.mycollection1.products %}
{% include 'product-loop' with settings.collection_sidebar %}
{% include 'pagination' with settings.collection_sidebar %}
</div>
{% endpaginate %}
{% paginate collections.mycollection2.products by settings.pagination_limit %}
<div style="clear:both;">
<h1>Title</h1>
{% assign products_per_row = "4" %}
{% assign limit = 50 %}
{% assign products = collections.mycollection2.products %}
{% include 'product-loop' with settings.collection_sidebar %}
{% include 'pagination' with settings.collection_sidebar %}
</div>
{% endpaginate %}
{% paginate collections.mycollection3.products by settings.pagination_limit %}
<div style="clear:both;">
<h1>Title</h1>
{% assign products_per_row = "4" %}
{% assign limit = 50 %}
{% assign products = collections.mycollection3.products %}
{% include 'product-loop' with settings.collection_sidebar %}
{% include 'pagination' with settings.collection_sidebar %}
</div>
{% endpaginate %}
This is what you can do by using pagination. I'm assuming all the three collections are of the same size.
First, let us call the pagination for all the products in your store
{% assign total_products = collections.all.products_count %}
Since you are shuffling 4 products at a time, the number of pages to be paginated will be total products count / 4
{% assign loop_value = total_products | divided_by: 4 %}
Now start the pagination
{% paginate collections by loop_value %}
Now we need to get a display window to work for the 4 product displays
{% assign window = paginate.current_page %}
{% assign window_start = window | minus: 1 | times: 4 %} //4 is the number of products being displayed
{% assign window_end = window | times: 4 | plus: 1 %}
Now this is done, we start product display. This is the same syntax for all the 3 collections, just replace the collection handle as required.
{% for product in collections.mycollection1.products %} // mycollection1 is your collection handle
{% if forloop.index > window_start and forloop.index < window_end %}
{% include 'product-block' %}
{% endif %}{% endfor %}
And we end the pagination
{% if paginate.pages > 1 %}<div class="pagination align-right">
{{ paginate | default_pagination }}
</div>{% endif %}{% endpaginate %}
Some important things to note very carefully:
Unless you create "mycollection1/2/3" arrays dynamically from the code, this code works only for the first 50 products in those collections.
You can use paginate function only once in an entire page.

Twig variable from one loop in another doesn't work

I have two loops in Twig. One is to check the stock level of a product, the other is to display some product options. I'm trying to create a variable from one loop which I can use inside the other to add a class name to a list(s).
I can't get that to work. Any help is more then welcome...
What I have is this:
{% set stockLevel = '' %}
{% for variant in product.variants %}
{% set stockLevel = variant.stock.level %}
{{ stockLevel }} // gives 1 0 1 1 (So all sizes except the second one are available)
{% endfor %}
{% for option in product.options %}
<ul class="optionslist">
{% if option.values %}
{% for value in option.values %}
<li class=" {% if stockLevel == 0 %}not_on_stock {% endif %}" >
<a class="item">etc...</a>
</li>
{% endfor %}
{% endif %}
</ul>
{% endfor %}
I believe there is something wrong the way you are handling your data. Right now, event if your variable was accessible in your second for loop, the value of it would be the one set lastly and your script would've failed anyway.
I can suggest a bit of a hacky way, feel free to improvise or use the idea behind it.
Say, we assign an empty array:
{% set variantsArray = [] %}
Then, we will use the filter merge to fill it with some dummy data(that's where your first loop comes into play)
{% for number in 1..5 %}
{% set variantsArray = variantsArray | merge([number]) %}
{% endfor %}
Then to be sure, we can access all the values there, we will just dump it out(that's where your second loop comes)
{% for index in 1..3 %}
{{ dump(variantsArray) }}
{% endfor %}
The following dump generated:
array:5 [▼
0 => 1
1 => 2
2 => 3
3 => 4
4 => 5
]
Hope you got the idea. Any suggestions are kindly welcome.
I am not sure what you are trying to do, how do you get your stock level from the variants? i think it is better to have a service function which help you get your stock level by product, the code would be cleaner :
controller action
$stock_helper = $this->container->get('stockhelper_service');
$stocklevel = $stock_helper->getStockLevelByProduct($product); //getstocklevelbyproduct handle your logic and return the stock level integer.
pass this variable to the twig.
One variable in your twig, no loops there, clean view, understandable code in your view and action.
Ok, I found the solution. The trick is to "connect" both values. Like so:
{% for option in product.options %}
{% if option.values %}
<ul id="product_configure_option2_{{ option.id }}" data-id="{{ option.id }}">
{% for value in option.values %}
{% set stock = null %}
{% for variant in product.variants **if variant.title == (option.title ~ ': ' ~ value.title)** %}
{% set stock = variant.stock %}
{% endfor %}
<li value="{{ value.id }}"{% if not stock.available %} class="not_on_stock"{% endif %}>{{ value.title }}</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}

Trying to locate some objects in a space

I'm trying to locate some objects in a field using Twig given some conditions (yes, I know it s simple, but I'm having too much troubles to have results). It's all right, but I'm having troubles giving some distance between objects.
This is my code:
{% for key, positions in teams %}
{% for key1, position in positions %}
{% for key2, player in position %}
{% set x = 100 %}
{% set counter = player.positionId|length + 1%}
{% set d = x/counter %}
{% if player.positionId == 1 %}
{% set top = 0.4 %}
{% set xpos = 42 %} //That value is correct because it's a goalkeeper
{% elseif player.positionId == 2%}
{% set top = 9.5 %}
{% set xpos = %}
{% elseif player.positionId == 3%}
{% set top = 20.5 %}
{% set xpos = %}
{% elseif player.positionId == 4%}
{% set top = 32.5 %}
{% set xpos = %}
{% endif %}
In summary, I have some players that are located on different y-coodinates given their positions, but also I need each one to have some distance between them if they have the same position, being xpos its position in the x-axis.
If you don't know Twig, you can help me using another lannguage too. Thanks in advance
Ah, and well, I want to know how to separate the teams (because it renders all players at this moment)
There are multiple ways to pass values to twig, one way is to do this through routing,
# app/config/routing.yml
blog_show:
path: /blog/{YOUR_VARIABLE_OR_VALUE_HERE}
defaults: { _controller: AcmeBlogBundle:Blog:show }
see this reference: http://symfony.com/doc/current/book/routing.html
you can also set global variables: http://symfony.com/doc/current/cookbook/templating/global_variables.html
or create an array in controller using repository or on your on, then send this to the view through the params
ie)
$repository2 = $this->getDoctrine()->getRepository('YOUR_BUNDLE_NAME_Bundle:YOUR_REPOSITORY');
$YOUR_ARRAY_NAME = $repository2->REPOSITORY_METHOD_OR_GENERIC_METHOD($IDENTIFIER);
$params = array('form' => $form->createView(), 'YOUR_ARRAY_NAME' => $YOUR_ARRAY_NAME');
return $this->render('SystemCheckoutBundle:Checkout:checkout.html.twig', $params);
these params can then be accessed in the view via:
{% for YOUR_ARRAY_NAME in YOUR_ARRAY_NAME %}
Client ID: {{ YOUR_ARRAY_NAME.contactId }}<br>
First Name: {{ YOUR_ARRAY_NAME.firstName }}<br>
Last Name:{{ YOUR_ARRAY_NAME.lastName }}<br>
Email: {{ YOUR_ARRAY_NAME.email }}<br>
{% endfor %}

Resources