Twig - How to randomise items in the array and loop them? - php

How can I randomise items in the array and loop them?
{% for item in article.resources|shuffle|slice(1) %}
...
{% endfor %}
I get this error:
Unknown "shuffle" filter in "partials/content.twig" at line 30.
If I use random():
{% for item in random(article.resources|slice(1)) %}
Nothing is returned.
Any ideas?
NOTES:
I don't want to use PHP btw.

Twig Array Extension already has a shuffle() filter (based on PHP shuffle())

Do something like that:
$twig = new Twig_Environment($loader);
$function = new Twig_SimpleFunction('shuffle', function ($array) {
shuffle($array);
return $array;
});
$twig->addFunction($function);
read more about it here
http://twig.sensiolabs.org/doc/advanced.html#functions

I used the Twig Array Extension, to make use of |shuffle. On my installation the extension wasn't loaded.
Added this to my config/services.yml, under services:
services:
twig.extension.array:
class: Twig_Extensions_Extension_Array
tags: [twig.extension]
Then you can use:
{% for item in items|shuffle %}
...
{% endfor %}

I think you will have to remove slice part of it.
Try this code and let me know if this works.
{% for item in random(article.resources) %}
{% endfor %}
You would probably like to keep some check in your for loop to ensure random is not returning same item twice.

Related

Display split results in Symfony2 twig

I have a function in my Symfony controller which returns the following:
test+testString1+test2
TestController.php
public function getResultAction($fileName) {
$string1="test";
$string2="testString1";
$string3="test2";
$response = $string1."+".$string2."+".$string3;
return new Response($response);
}
In my twig I've rendered the function in my controller:
test.html.twig
{% set test %}
{{ render(controller('TestBundle:Test:getResult')) }}|split('+', 4)
{% endset %}
{{ test[0] }}
I'm using twig split filter so that I can display test, testString1 and test2 individually. But then whenever I attempt to display test[0], I receive the following error:
Impossible to access a key "0" on an object of class "Twig_Markup" that does not implement ArrayAccess interface in TestBundle:Test:test.html.twig
What's wrong with what I'm doing? I hope you could help me with this. Thanks
You miss the split filter inside the double brace as follow:
{% set test %}
{{ render(controller('TestBundle:Test:getResult')) |split('+', 4) }}
{% endset %}
Hovenever seems same problem with the set tag. From the doc:
The set tag can also be used to 'capture' chunks of text
Try with:
{%
set test=render(controller('TestBundle:Test:getResult'))|split('+', 4)
%}
Hope this help

How to make array of objects in Twig?

I'm new to TWIG...
Within a TWIG template, I need to create a new array of objects that is a subset of an existing array of objects (products).
So far, I have this:
{% set onlyLimitedAmtProds = [] %}
{% for product in products %}
{% if product.in_stock_amount < 3 %}
{% set onlyLimitedAmtProds = onlyLimitedAmtProds|merge(product) %}
{% endif %}
{% endfor %}
The above generates an error of:
"The merge filter only works with arrays or hashes; array and object given."
I'm not sure of the syntax to make this work.
You are actually trying to merge array onlyLimitedAmtProds with object product. But merge function works only with two arrays. You need to merge with an array of one element: merge([product]).

Name of Twig variable in a Twig variable

I get a list of collections right from Doctrine and I store them into an array.
For example:
$data['collList'] = $heyDoctrine->giveMeMyColls();
But I also want to retrieve some informations about these collections.
I store it into $data['collectionId'].
Until this point, everything works fine.
But in my Twig template, I want to create ordered lists with the name of my collection and every item of this list would be an information about this collection.
So, in PHP, I would do this:
foreach($data['collList'] as $collItem){
echo $collItem['name'];
echo '<ul>';
foreach($data[$collItem['id']] as $collItemData){
echo '<li>'.$collItemData.'</li>';
}
}
My problem is: how to do this with Twig?
I don't know how to say to Twig «hey, use «coll.id» as THE NAME of an other variable!
I've looked a bit and I've found the «attribute» function, but I wasn't able to make it work.
How should I do that?
Thanks a lot.
So, try next twig code:
{% for key, collItem in data.collList %}
{{ collItem.name }}
<ul>
{% for collItemData in data[collItem.key] if key == 'id' %}
<li> {{ collItemData }} </li>
{% endfor %}
</ul>
{% endfor %}

"While" and "repeat" loops in Twig

Are there any nice ways to use while and repeat loops in Twig? It is such a simple task, but without macros I can't find anything nice and simple.
At least do an infinite cycle and then break it in a condition?
EDIT:
I mean something like
do {
// loop code
} while (condition)
or
while (condition) {
// loop code
}
Edit 2:
Looks like it is not supported natively by twig same reason as it is not supported neither continue; or break; statements.
https://github.com/twigphp/Twig/issues/654
You can emulate it with for ... in ... if by using a sufficiently-high loop limit (10000?)
while
PHP:
$precondition = true;
while ($precondition) {
$precondition = false;
}
Twig:
{% set precondition = true %}
{% for i in 0..10000 if precondition %}
{% set precondition = false %}
{% endfor %}
do while
PHP:
do {
$condition = false;
} while ($condition)
Twig:
{% set condition = true %} {# you still need this to enter the loop#}
{% for i in 0..10000 if condition %}
{% set condition = false %}
{% endfor %}
I was able to implement a simple for loop in twig. So the following php statement:
for ($x = 0; $x <= 10; $x++) {
echo "The number is: $x <br>";
}
when translated to twig is:
{% for i in 0..10 %}
* {{ i }}
{% endfor %}
It's not a while loop but a potential workaround. The best suggestion is to leave business logic like this out of the template layer.
In a nutshell: no. This functionality implies advanced logic, which should be in your business logic, not in the template layer. It's a prime example of the separation of concerns in MVC.
Twig supports for-loops completely, which should suffice if you code correctly - being that complex conditional decisions on which data to display are taken in the business logic where they belong, which then pass a resulting array 'ready to render' to the templates. Twig then supports all nice features only needed for rendering.
This is possible, but a little bit complicated.
You can use {% include ... %} to process nested arrays, which from the comments I read is what you need to do.
Consider the following code:
nested_array_display.html
<ul>
{% for key, val in arr %}
<li>
{{ key }}:
{% if val is iterable %}
{% include 'nested_array_display.html' %}
{% else %}
{{ val }}
{% endif %}
</li>
{% endfor %}
</ul>
Warning with the top solution with "high loop limit" : the loop doesn't break when the condition returns false, it just doesn't enter the loop. So the loop runs up to the high indice

Setting element of array from Twig

How can I set member of an already existing array from Twig?
I tried doing it next way:
{% set arr['element'] = 'value' %}
but I got the following error:
Unexpected token "punctuation" of value "[" ("end of statement block"
expected) in ...
There is no nice way to do this in Twig. It is, however, possible by using the merge filter:
{% set arr = arr|merge({'element': 'value'}) %}
If element is a variable, surround it with brackets:
{% set arr = arr|merge({(element): 'value'}) %}
I ran into this problem but was trying to create integer indexes instead of associative index like 'element'.
You need to protect your index key with () using the merge filter as well:
{% set arr = arr|merge({ (loop.index0): 'value'}) %}
You can now add custom index key like ('element'~loop.index0)
If initialization only need:
{% set items = { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown' } %}
I have tried #LivaX 's answer but it does not work , merging an array where keys are numeric wont work ( https://github.com/twigphp/Twig/issues/789 ).
That will work only when keys are strings
What I did is recreate another array ( temp) from the initial array (t) and make the keys a string , for example :
{% for key , value in t%}
{% set temp= temp|merge({(key~'_'):value}) %}
{% endfor %}
t keys : 0 , 1 , 2 ..
temp keys : 0_, 1_ , 2_ ....
You can also use the following syntax:
{% set myArray = myArray + myArray2 %}
Just use this like {% set arr={'key':'value'} %} (with no blank space after the :), it works well.
But when I use it inside a for loop, to make it an array, it does not work outside of the for scope.
{% for group in user.groups %}
{% set foo={'loop.index0':'group.id'} %}
{% set title={'loop.index0':'group.title'} %}
{{ title }} //it work
{% else %}
{% set foo={'0':'-1'} %}
{% set title={'0':'未分组'} %}
{% endfor %}
{{ title }} //it does not work, saying title is not defined
{% set links = {} %}
{# Use our array to wrap up our links. #}
{% for item in items %}
{% set links = links|merge({ (loop.index0) : {'url': item.content['#url'].getUri(), 'text': item.content['#title']} }) %}
{% endfor %}
{%
set linkList = {
'title': label,
'links': links
}
%}
{% include '<to twig file>/link-list.twig'%}
Thanks for this thread -- I was also able to create an array with (loop.index0) and send to twig.
I've found this issue very annoying, and my solution is perhaps orthodox and not inline with the Twig philosophy, but I developed the following:
$function = new Twig_Function('set_element', function ($data, $key, $value) {
// Assign value to $data[$key]
if (!is_array($data)) {
return $data;
}
$data[$key] = $value;
return $data;
});
$twig->addFunction($function);
that can be used as follows:
{% set arr = set_element(arr, 'element', 'value') %}
Adding my answer in case anyone needs to update the array when merge doesn't work because it just appends to the end of an array instead of providing the ability to change an existing value.
Let's say you have an array words_array like below:
Object {
0: "First word"
1: "Second word"
2: "Third word"
}
In order to update "Second word", you can do the following:
{% set words_array = {(1): 'New word'} + words_array %}
The resulting array would be:
Object {
0: "First word"
1: "New word"
2: "Third word"
}
You can take it one step further if you are using a loop and use the loop.index0 variable something like the following:
{% for word in words_array %}
{% if word == 'Second word' %}
{% set words_array = {(loop.index0): 'New word'} + words_array %}
{% endif %}
{% endfor %}
You can declare the array as follows
{% set arr = [{'element1': 'value1','element2' : 'value2'},{'element1': 'value1','element2' : 'value2'},{'element1': 'value1','element2' : 'value2'}] %}
I had a multi dimension array. The only solution I could find out is create a new temporary array and update/add the information, which was further passed on to another twig function.
I had this problem sometime ago. Imagine you have an array like this one:
data = {
'user': 'admin',
'password': 'admin1234',
'role': 'admin',
'group': 'root',
'profile': 'admin',
'control': 'all',
'level': 1,
'session': '#DFSFASADASD02',
'pre_oa': 'PRE-OA',
'hepa_oa': 'HEPA-OA',
'pre_ra': 'HEPA-RA',
'hepa_ra': 'HEPA-RA',
'deodor_ra': 'DEODOR-RA'
}
So, you want to show this data in two rows, but remove the password from that list. To this end, split in 2 arrays will be easy with the slice filter. However, we have to remove the password. For that reason, I'm using this snippet. The idea is to put all the elements lesser than the data elements size divided by 2. To calculate this we use the filter length. Now to get the index of the current element we user loop.index. And finally we *push an associative element in the left or right array. An associative array has two components key and value. To reference an array key in twit we operator () and we use the merge filter to push into the array as shown here {% set left_list = left_list|merge({ (key): value }) %}
This is the complete solution.
{% set left_list = {} %}
{% set right_list = {} %}
{% set limit = data|length // 2 %}
{% for key, value in data|cast_to_array %}
{% if key != 'password' %}
{% if loop.index <= limit %}
{% set left_list = left_list|merge({ (key): value }) %}
{% else %}
{% set right_list = right_list|merge({ (key): value }) %}
{% endif %}
{% endif %}
{% endfor %}
{% for key, value in left_list %}
<p>
<label for="{{key}}">{{key}}</label>
<input type="text" name="{{key}}" id="{{key}}" value="{{value}}"
class="text ui-widget-content ui-corner-all">
</p>
{% endfor %}

Categories