Shopware 6 App - Twig Script causes memory overflow - twig

We have written us a script that allows us in Shopware 6 badges per categories. With many products crashes sometime after 5 minutes shopware crashes with the log message that the memory at php is not enough.
Have we done anything wrong here?
We have already packed the elements in an array (the graphics) and fetch them bundled from the server.
{# #var services \Shopware\Core\Framework\Script\ServiceStubs #}
{% set page = hook.page %}
{# #var page \Shopware\Storefront\Page\Product\ProductPage #}
{% set products = [] %}
{% if hook.page.cmsPage.type === 'product_list' %}
{% foreach hook.page.cmsPage.sections as section %}
{% foreach section.blocks as sectionBlock %}
{% if sectionBlock.type !== 'product-listing' %}
{% continue %}
{% endif %}
{% foreach sectionBlock.slots as slot %}
{% if slot.type !== 'product-listing' %}
{% continue %}
{% endif %}
{% foreach slot.data.listing.entities as product %}
{% set products = products|merge([product]) %}
{% endforeach %}
{% endforeach %}
{% endforeach %}
{% endforeach %}
{% endif %}
{% set categoryIds = products|reduce((carry, v) => carry|merge(v.categoryTree), []) %}
{% if categoryIds %}
{% set categories = services.repository.search('category', {'ids': categoryIds}) %}
{% set mediaIds = [] %}
{# Loop through all products and collect media ids #}
{% foreach products as product %}
{% set badgeList = [] %}
{% foreach categories.entities.getList(product.categoryTree) as category %}
{% if category.customFields.minds_cd_show_badge %}
{% set badgeList = badgeList|merge([{
'priority': category.customFields.minds_cd_badge_priority,
'mediaId': category.customFields.minds_cd_badge
}]) %}
{% endif %}
{% endforeach %}
{% set badge = (badgeList|sort((a, b) => a.priority <=> b.priority))|last %}
{% if badge %}
{% set mediaIds = mediaIds|merge([ badge.mediaId ]) %}
{% endif %}
{% endforeach %}
{# Get all badge media ids in a performant way #}
{% set mediaCollection = services.repository.search('media', {'ids': mediaIds}) %}
{# Loop through all products again and attach the badge #}
{% foreach products as product %}
{% set badgeList = [] %}
{% foreach categories.entities.getList(product.categoryTree) as category %}
{% if category.customFields.minds_cd_show_badge %}
{% set badgeList = badgeList|merge([{
'priority': category.customFields.minds_cd_badge_priority,
'mediaId': category.customFields.minds_cd_badge
}]) %}
{% endif %}
{% endforeach %}
{% set badge = (badgeList|sort((a, b) => a.priority <=> b.priority))|last %}
{% if badge %}
{% set badgeMedia = mediaCollection.get(badge.mediaId) %}
{% do product.addArrayExtension('mcb', {
'badge': badgeMedia
}) %}
{% endif %}
{% endforeach %}
{% endif %}

I think a real performance killer here might be that you potentially fetch a lot of categories. At least that could be the case if the category tree has many levels, with many categories on each level and products are assigned to multiple categories on different branches.
If I read this correctly you're really only interested in categories that have the custom field minds_cd_show_badge set to true. I assume those would potentially far less categories than the total number of entities you're fetching right now.
You could try setting a filter in your criteria targeting your custom field so you're only fetching the categories that are relevant in your case.
{% set criteria = {
'ids': categoryIds,
'filter': [
{ 'field': 'customFields.minds_cd_show_badge', 'type': 'equals', 'value': true }
]
} %}
{% set categories = services.repository.search('category', criteria) %}

Related

Iterating over Shopware Array and trying to get the key not working

Hey iam currently trying to get the description of the first menu navigation in Shopware 6.
For that i use the array page.header.navigation.active.breadcrumb and use its key in page.header.navigation.tree[key].description, but my key value is empty.
Thats happening due to the key beeing empty for no reason.
Heres my Code:
{% sw_extends "#Storefront/storefront/section/cms-section-sidebar.html.twig" %}
{% set topMenu = null %}
{% for key, value in page.header.navigation.active.breadcrumb %}
{% if loop.index == 2 %}
{% set topMenu = value %}
{# {% set topMenuDescription = page.header.navigation.tree[key].category.description %} #}
{% set topMenuDescription = key %}
{% endif %}
{% endfor %}
{% set currentMenu = page.header.navigation.active.breadcrumb | last %}
{# {% set currentMenu = page.header.navigation.active.name %} #}
{# {% set topMenuDescription = page.header.navigation.active.description %} #}
{# {% if ! topMenuDescription %}
{% set topMenuDescription = page.header.navigation.active.description %}
{% endif %} #}
{% block section_main_content_block %}
<div class="category-top">
<div class="category-banner">
<img src="/media/6a/fd/8b/1632946677/listing-banner.jpg">
<div class="category-banner-headlines">
{% if (currentMenu != topMenu) %}
<h3>{{ topMenu }}<h3>
<h2>{{ currentMenu }}<h2>
{% else %}
<h2 class="sameMenu">{{ currentMenu }}<h2>
{% endif %}
</div>
</div>
<div class="category-description">
<h1>{{ currentMenu }}</h1>
{{ topMenuDescription | trans | raw }}
</div>
</div>
{{ parent() }}
{% endblock %}
Also here is the structure of the key i want to get:
key-i-want-to-get
And heres the description i want to get:
description-i-want-to-get
Sidenote: The description in my example is empty, since i do the showcase in a seperate testing area, where i havent set a description
The reason topMenuDescription is empty is because the variable only exist inside the scope of the {% for %}-loop you've created. Outside this loop the variable doesn't exist.
In order to solve this issue you need to alter the scope of topMenuDescription by defining the variable outside the {% for %}-loop
{% set topMenuDescription = null %}
{% for key, value in page.header.navigation.active.breadcrumb %}
{% if loop.index == 2 %}
{% set topMenu = value %}
{# {% set topMenuDescription = page.header.navigation.tree[key].category.description %} #}
{% set topMenuDescription = key %}
{% endif %}
{% endfor %}
sidenote
You really should enable twig's debug whilst developing as your current snippet would throw a RuntimeError explaining the variable does not exist.

How to break “for loop” in Django template

My code is:
{% for key, value in section.items %}
{% for key_t, value_t in title.items %}
{% if value_t.section_id == key|add:"0" %}
<li class="nav-item-header"><div class="text-uppercase font-size-xs line-height-xs">
{{value.title}}</div> <i class="icon-menu" title="Tables"></i></li>
{% endif %}
{% endfor %}
{% endfor %}
I want to break the for loop when if the condition is true. like as
{% for key, value in section.items %}
{% for key_t, value_t in title.items %}
{% if value_t.section_id == key|add:"0" %}
<li class="nav-item-header"><div class="text-uppercase font-size-xs line-height-xs">
{{value.title}}</div> <i class="icon-menu" title="Tables"></i></li>
{{break}}
{% endif %}
{% endfor %}
{% endfor %}
How is it possible? please help me...
There is no way to break out of a for loop in Django Template. However, you can achieve this by setting a variable and adding an if statement on the top like this.
{% set isBreak = False %}
{% for number in numbers %}
{% if 99 == number %}
{% set isBreak = true %}
{% endif %}
{% if isBreak %}
{# this is a comment. Do nothing. #}
{% else %}
<div>{{number}}</div>
{% endif %}
{% endfor %}
for some additional help check out this link
https://dev.to/anuragrana/for-loops-in-django-2jdi
or check this answer on stack overflow
How to break "for loop" in Django template

Twig embed and variable scope

I'm running into some problems when nesting twig embeds, more particularly when both embeds have the same variable defined.
An example: a simple code excerpt from a form layout with a fieldset, some rows and input elements (removed some variables for clarity):
{% embed 'components/frmGroup' with {'id':'myFieldset'} %}
{% block main %}
{% embed 'components/frmRow' with {'id':'mySpecialFormRow'} %}
{% block main %}
{% include 'components/inpText' %}
...
{% endblock %}
{% endembed %}
{% embed 'components/frmRow' %}
{% block main %}
{% include 'components/inpText' %}
...
{% endblock %}
{% endembed %}
{% endblock %}
{% endembed %}
As you can see, both frmGroup (fieldset) and frmRow (row) components can take a variable id. The first frmRow embed is fine since it defines its own id, the problem arises with the second frmRow which doesn't need a specific id (and so doesn't define one). But when I look at the outputted html code, I see the formrow carries #myFieldset, the id set on the frmGroup embed. Not what I wanted to accomplish :)
I've tried toying with the "only" keyword but that gave me some very weird results. I could use different variable names (frmGroupId and frmRowId) but that feels lame (and creates clutter), I could also explicitly define and empty id on the second frmRow, but then I have to remember what variables to define on what nested embeds, which isn't pretty either.
So how do I solve this (and can it be solved in Twig)?
I tried your example and I have perfectly normal results:
{% set id = "1" %}
{# id is 1 #}
{% embed "_test.html.twig" with { id: "2"} only %}
{# id is 2 for the scope #}
{% block main %}
{% embed "_test.html.twig" with { id: "3"} %}
{# id is 3 for the scope #}
{% endembed %}
{% embed "_test.html.twig" %}
{# id value is still 2 #}
{% endembed %}
{% endblock %}
{% endembed %}
{% embed "#_test.html.twig" %}
{# id value is 1 #}
{% endembed %}
With _test.html.twig:
{% block main %}
{{ id|default('null') }}
{% endblock %}
It returns
3 2 1
The only think i can think of it for you to use temporary variable:
{% embed "#InddigoMain/_test.html.twig" with { id: "2"} %}
{% block main %}
{% embed "#InddigoMain/_test.html.twig" with { id: "3"} %}
{% endembed %}
{% set temp = id %}
{% set id = null %}
{% embed "#InddigoMain/_test.html.twig" %}
{% endembed %}
{% set id = temp %}
{% endblock %}
{% endembed %}
{% embed "#InddigoMain/_test.html.twig" %}
{% endembed %}
It returns
3 null 1
I have go throught the twig bundle and I don't think it exist.

Passing Twig variables from array into an include

I have an array I'm iterating over to pull in different types of components into my page:
array(
'content'=> array(
'componentA'=>array(
'val'=>'1',
'title'=>'sample title'
),
'componentB'
)
)
I'm attempting to pass variables through from the array to the included template, but I'm not sure how to turn that string produced by the join into something that the include can understand as an array of variables. When I exclude the "with" from the first #components include, it prints out all the default values I've set in the iterable components like I would expect, but still gives me a white screen when I keep the with attribute in. When I display var itself, it returns this string:
(Note, I've also tried putting quotes around the {{k}} to no avail)
{ val:'1',title:'sample title' }
How can I pass the variables from my array to my component?
{% for key,item in content %}
{% if item is iterable %}
{% set var = [] %}
{% for k,v in item %}
{% set temp %}{% if loop.first %} { {% endif %}{{ k }}:'{{ v }}'{% if loop.last %} } {% endif %}{% endset %}
{% set var = var|merge([ temp ]) %}
{% endfor %}
{% set var = var|join(',') %}
{{ include ("#components/" ~ key ~ ".tmpl",var) }}
{% else %}
{{ include ("#components/" ~ item ~ ".tmpl") }}
{% endif %}
{% endfor %}
Your include statements are incorrect. You are using {{ include ... }}, which should be {% include ... %}.
The following snippet should work, if you only want to provide the data from the array (and not the loop data):
{% for key,item in content %}
{% if item is iterable %}
{% include ("#components/" ~ key ~ ".tmpl") with item %}
{% else %}
{% include ("#components/" ~ item ~ ".tmpl") %}
{% endif %}
{% endfor %}
You can then use {{ val }} and {{ title }} within your component template.
If you want to include the loop data, you can use:
{% for key,item in content %}
{% if item is iterable %}
{% include ("#components/" ~ key ~ ".tmpl") with {item: item, loop: loop} %}
{% else %}
{% include ("#components/" ~ item ~ ".tmpl") %}
{% endif %}
{% endfor %}
You can then use {{ item.val }}, {{ item.title }} and {{ loop.index }} in your component template.

twig, cannot loop hash called by variable name

Why does this not work:
{% set relations = [{'cat':'friends','foo':'bar1'},{'cat':'enemies','foo':bar2},....] %}
{% set friends = [{'firstName':'John', 'lastName':'Goodman'},....] %}
{% set enemies = [{'firstName':'Ron', 'lastName':'Badguy'},....] %}
{% for relCat in relations %}
{% set list = relCat.cat %}
{% for person in list %}
{{ person.firstName }}
{% endfor %}
{% endfor %}
I admit that I 'm pretty new to twig, so I really searched and searched, but cannot find a solution for my problem (that I thought to be trivial) ....
I hope someone can help - I lost all my hair over this, thanks, Rudolph
Order of variables is important
So first: enemies and friends:
{% set friends = [{'firstName':'John', 'lastName':'Goodman'}] %}
{% set enemies = [{'firstName':'Ron', 'lastName':'Badguy'}] %}
Then set relations: again friends not 'friends', first is variable, second a string:
{% set relations = [{'cat':friends,'foo':'bar1'},{'cat':enemies,'foo':bar2}] %}
{% for relCat in relations %}
{% set list = relCat.cat %}
{% for person in list %}
{{ person.firstName }}
{% endfor %}
{% endfor %}
And this should work
See fiddle
You can also use the _context variable if you really need to keep strings:
{% set relations = [{'cat':'friends','foo':'bar1'},{'cat':'enemies','foo':bar2}] %}
{% set friends = [{'firstName':'John', 'lastName':'Goodman'}] %}
{% set enemies = [{'firstName':'Ron', 'lastName':'Badguy'}] %}
{% for relCat in relations %}
{% set list = _context[relCat.cat] %} {# <--- here #}
{% for person in list %}
{{ person.firstName }}
{% endfor %}
{% endfor %}
See fiddle

Resources