Incremental for-loop in twig - php

I'm trying to add values in an array using twig. Does twig have an incremental for-loop feature? I'm aware of the standard for-loop in twig (e.g. {% for k in v %}), but I'm interested in a loop where I may specify things as detailed below in pure PHP:
<?php
//adding values in an array
$quantities = array('23', '23', '4', '45', '45');
$sum = 0;
for ($i = 0; $i < count($quantities); $i++) {
$sum += $quantities[$i];
}
echo "Sum: " . $sum . "\n";
?>
This is pretty much exactly what I'm looking to do with twig.
Thanks for any help, All.

You can check the Loop options in Twig
{% for quantity in quantities %}
// Do your stuff here with each individual quantity
// If you want to access the index ($i in your php sample)
{{ quantities[loop.index0] }}
{% endfor %}
Just take it as a reference and check the link provided to adapt it to your configuration.

Your approach could be improved even in php "natively"
Do you know about array_sum?
And of course, in twig you can create a twig_extension that in tandem with twig_filter could help you obtain what you want in a smart way.
To create a twig_extension with custom filter (remember to follow link that I have provided for "twig estension"):
public function getFilters()
{
return array(
new \Twig_SimpleFilter('sum', 'array_sum'),
);
}
Then you can use sum keyword into your twig template, as a filter of course
{% set sum = quantities|array_sum %}

If you really want the twig representation of your PHP code, this will be:
{% set sum = 0 %}
{% for value in quantities %}
{% set sum = sum + value %}
{% endfor %}
Sum: {{ sum }}
Anyway, #DonCallisto's approach is better.

Related

Twig print available from string

How to print available form string in twig
In my code php
$data['fruits'] = array('apple', 'banana', 'orange');
$data['help_apple'] = 'This is help apple';
$data['help_banana'] = 'This is help for banana';
$data['help_orange'] = 'This is help for orange';
In twig template
{% for fruit in fruits %}
{{ "help_" ~ fruit }}
{% endfor %}
The print screen is help_apple, help_banana, help_orange
How to print correct data i need for help_ fruit key ?
You need to use the attribute function with _context. Tested on twigfiddle.com. Hope this helps.
{% for fruit in fruits %}
{# Here is how you do it #}
{{ attribute(_context, 'help_'~ fruit) }}
{% endfor %}
The _context variable holds all variables in the current context. Instead of using the attribute function, you can access values of the _context array with the regular bracket notation as well:
{% for fruit in fruits %}
{{ _context['help_' ~ fruit] }}
{% endfor %}
I would personally do it this way as it's more concise and in my opinion clearer.
You might want to check for undefined variables when accessing values of the _context array. I have written about it in my answer to this question: Symfony2 - How to access dynamic variable names in twig.
You also asked whether something like this is possible:
{% set attribute(context, 'help' ~ fruit, "value") %}
That's not possible. If you want to set variables with dynamic names, you need to create a Twig extension. Take a look at my answer to this question: How to set a variable name with dynamic variables?
But, like #user9189147 mentioned, it would be easier and in my opinion clearer if you instead created a new array to hold the help values. Then you wouldn't need to create an extension.
$data['fruits'] = ['apple', 'banana', 'orange'];
$data['help'] = [];
$data['help']['apple'] = 'This is help apple';
$data['help']['banana'] = 'This is help for banana';
$data['help']['orange'] = 'This is help for orange';
{% for fruit in fruits %}
{{ help[fruit] }}
{% endfor %}
Then you can set new values to the help values in Twig using the merge filter, like this (though I don't know why you would want to do it in Twig):
{# Set single value #}
{% set help = help|merge({
banana: 'New help for banana',
}) %}
{# Or multiple values #}
{% set help = help|merge({
apple: 'An apple a day keeps the doctor away',
orange: 'Orange is the new black',
}) %}

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

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.

"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

Symfony2 Twig Get Total Count for Child Entity

The following entities exist, Farm, Barn and Animals. A Farm can have many Barns and a Barn many Animals.
When displaying a Farm in a TWIG template the number of Animals should be shown as well.
What is the best way to do this?
I have create a TWIG extension which allows me to easily show the number of barns.
public function totalFieldFilter($data, $getField='getTotal') {
$total = count($data->$getField());
return $total;
}
In my template I would use {{ farm|totalField('getBarns') }}, I could easily extend this to write another custom function like so:
public function totalFieldFilter($farm) {
$total = 0;
foreach($farm->getBarns() AS $barn) {
$total += count($barn->getAniamls());
}
return $total;
}
Although this would work, is there a better way and can it be made more generic? What if I wanted to count Legs on Animals? Or how many Doors a Barn has, I would have to write a custom TWIG extension each time.
Use Entity accessors :
{% for farm in farms %}
{{ farm.name }}
{% set barns = farm.getBarns() %}
Barns count = {{ barns|length }}
{% for barn in barns %}
{% set animals = barn.getAnimals() %}
{{ barn.name }} animals count : {{ animals|length }}
{% endfor %}
{% endfor %}
You are looking for the length filter
When used with an array, length will give you the number of items. So, if your array is farm.barns, you can just use {{ farm.barns|length }}

Twig - Using multiple child templates within parent

I have the following two templates
parent.html
<ul class="basketItems">
{% for item in items %}
{{ item | raw }}
{% endfor %}
</ul>
child.html
<li>
{{ link.title}}
</li>
Now i would like to have multiple instances of child.html within parent.html. In My php code I have to loop through the children and pass in the link object so that the link.title variable can be populated.
My current code involves me loading in parent.html, then rendering each child and creating a php array, then rendering the parent.html and passing in all the generated html of the children as array entries (see below). Is there any easy way to do this without having to build up a php array of html snippets by possibly using Twig blocks.
$parent = $twig->loadTemplate("parent.html");
foreach ($items as $item) {
$child = $twig->loadTemplate("child.html");
var $link = link::get($item->id));
/* do some other database retreival / data processing work */
$childHtml[] = $child->render(array('item' => $link));
}
$parent->render(array('items' => $childHtml));
Thanks in advance
try this:
{% for item in items %}
{% include "child.html" %}
{% endfor %}
Here in Manual: http://twig.sensiolabs.org/doc/templates.html
And for PHP Part:
$parent = $twig->loadTemplate("parent.html");
for ($i =0; $i < count($items); $i++) {
/* do some other database retreival / data processing work */
/* add additional information to array */
$items[i]['link'] = link::get($item->id));
}
$parent->render(array('items' => $childHtml));
Do the controller stuff and pass that clean array to template engine. Don't mix that.
It is always better to follow "Separation of concerns" principle:
http://en.wikipedia.org/wiki/Separation_of_concerns

Categories