Symfony2 Twig Get Total Count for Child Entity - php

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 }}

Related

How to iterate over many properties in Twig loop?

I have a problem with properties in (probably) Twig. I have controller in Symfony where getCategories(), getWords(), getTranslations() methods (from Doctrine) return the objects (relations). Every property in the controller is an array because I call findAll() method (from Doctrine again) which returns the array. Finally I return all the properties from controller to view (Twig file) where I try display the results by Twig for loop.
The problem is the Twig loop only iterates over flashcards property (I know why ;)) and I have no idea how to make many-properties iterating. I'd like the loop to iterate over all properties returned by the controller.
In the controller foreach loop I tried update the flashcards array with new associative keys such as: category, word and translation so that all the results returned by Doctrine (including relations) are stored in one flashcards property but then Symfony throws exceptions.
I wondered if create one array in the controller to which I would push the flashcards, cateogry, word and translation arrays and then return this one array to the view but I don't think this is good practice.
Here's the controller method code:
public function showAllCards()
{
$flashcards = $this->getDoctrine()->getRepository(Flashcards::class)
->findAll();
foreach ($flashcards as $flashcard) {
$category = $flashcard->getCategories()->getName();
$word = $flashcard->getWords()->getWord();
$translation = $flashcard->getTranslations()->getWord();
}
return $this->render('try_me/index.html.twig', [
'flashcards' => $flashcards,
'category' => $category,
'word' => $word,
'translation' => $translation
]);
}
Here's the Twig loop code:
{% for flashcard in flashcards %}
{{ word }}
<br>
{{ flashcard.pronunciation }}
<br>
{{ flashcard.exampleSentence }}
<br>
{{ category }}
<br>
{{ translation }}
<br>
{% endfor %}
I tried to execute the following controller code...
public function showMeAll()
{
$flashcards = $this->getDoctrine()->getRepository(Flashcards::class)
->findAll();
foreach ($flashcards as $flashcard) {
$flashcards['categories'] = $flashcard->getCategories()->getName();
$flashcards['words'] = $flashcard->getWords()->getWord();
$flashcards['translations'] = $flashcard->getTranslations()->getWord();
}
return $this->render('try_me/index.html.twig', [
'flashcards' => $flashcards,
]);
}
...with the following Twig loop...
{% for flashcard in flashcards %}
{{ flashcard.words }}
<br>
{{ flashcard.pronunciation }}
<br>
{{ flashcard.exampleSentence }}
<br>
{{ flashcard.categories }}
<br>
{{ flashcard.translations }}
<br>
{% endfor %}
...but then Symfony says:
An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Object of class Proxies__CG__\App\Entity\Words could not be converted to string").
Could you give me some tips how to solve this problem, please? I'd like the Twig loop to iterates over many properties (flashcard, word, category, translation). Or write if there's a better solution, please.
Thank you in advance for every answer!
According to your snippets, I'm guessing you want something like the following:
{% for flashcard in flashcards %}
{% for word in flashcard.getWords() %}
{{ word }}<br />
{% endfor %}
{{ flashcard.getPronunciation() }}<br>
{{ flashcard.getExampleSentence() }}<br>
{% for category in flashcard.getCategories()() %}
{{ category.getName() }}<br />
{% endfor %}
{% for translation in flashcard.getTranslations() %}
{{ translation.getWord() }}<br />
{% endfor %}
{% endfor %}
Have a look at this section of the documentation. Basically if you had foo.bar, twig will test if bar is a public property of foo and if not test if there is a public getter, getBar, to fetch bar.
Some sidenotes in both of your loops, the values category, word and translation will only hold the last value of your flashcards, because you are overwriting the value each time.

Loop through categories, childcategories and products in twig

My table structure in my database is like this:
Some categories have a child category and some not. A product can belong to:
Child category
Parent Category (this category has NO child categories)
My array looks like this:
Category A is a parent category. Category B - Head is also a parent category. Category B - Child is a child category of B - Head.
Now I would like to show this array like this:
But I'm stuck on how to know if it's a category or a list of products. Can someone help me with this?
If you're using Doctrine Models (which if you're using Symfony, you should be) then all you're doing is looping through the methods on the object.
Quick and dirty example with few assumptions e.g. using #Template() annotation and standard DAOs aka EntityManager[s] as well as having getChildren() and getProducts() methods on the Category.php (AKA model/entity)
On the controller
/**
* #Route("/products", name="all_products")
* #Template()
*/
public function someAction()
{
...
$categories = $this->getCategoryManager()->findBy([]);
...
return [
'categories' => $categories
];
}
In your twig template
{% if categories|length > 0 %}
{% for category in categories %}
{% if category.children|length > 0 %}
... Here you create the HTML for nested ...
{% else %}
... Here you create the HTML for Category ...
{% for product in category.products %}
... Here you create the HTML for products ...
{% endfor %}
{% endif %}
{% endfor %}
{% else %}
.... some html to handle empty categories ....
{% endif %}
If the HTML for nested is repeated in the HTML for flat (very likely scenario) then you can create and include a macro to spit that out for you.
This is basic, but I think it pretty much covers what you're asking if I'm understanding your question properly.
Btw, you should definitely read the docs for twig and Symfony since they have examples like these everywhere.
I'll edit this answer if you respond as appropriate. Right now you haven't posted enough information to really guide you properly but hope this helps.
You could use a recursive macro. In the macro you either print the list of products or print the list of categories and then call itself.. and so on...
{% macro navigation(categories, products) %}
{% from '_macros.html.twig' import navigation %}
{% if categories|length > 0 or products|length > 0 %}
<ul>
{% for category in categories %}
<li>
{{ category.name }}
({{ category.children|length }} child(ren) & {{ category.products|length }} products)
{{ navigation(category.children, category.products) }}
</li>
{% endfor %}
{% for product in products %}
<li>{{ product.name }}</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
You would just use this in a template like...
{% from '_macros.html.twig' import navigation %}
{{ navigation(array_of_categories) }}
This just creates a basic set of nested unordered lists but then can be used with any HTML you want, obviously.
For a fiddle see http://twigfiddle.com/mzsq8z).
The fiddle renders as the following (twigfiddle only shows the HTLM rather than something you can use to visualize)...

Limit, pagination in Symfony

I am trying to add pagination to my current project. I am pretty new to Symfony so I am not sure if there is something out there that can help me build such. My current code looks like this:
Controller class:
class MovieDisplayController extends Controller
{
public function showAction()
{
$movies = $this->getDoctrine()->getEntityManager()->getRepository('AppBundle:Movie')->FindAll();
return $this->render('movies/index.html.twig', array(
'movies' => $movies
));
}
}
Twig template:
{% block body %}
{% if movies|length == 0 %}
There are no movie items available. Add a movie here to get started.
{% elseif movies|length != 0 %}
These are the results: <br />
<ul>
{% for x in movies %}
<li>Title: {{ x.title }} - Price: {{ x.price }} - Edit - Details - Delete</li>
{% endfor %}
</ul>
Add more movie entries
{% endif %}
{% endblock %}
This will return all results within the database. I would like to only show 5 results (rows per page) and add paging buttons below the list and I wonder how/if this is possible?
The findAll() function will not work if you want to set limit.
You can try KnpPaginatorBundle to add pagination in symfony. It will work with fine to add pagination.
https://github.com/KnpLabs/KnpPaginatorBundle

"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

Is there any way to get average values from an array when using a Twig template?

Is there any way of getting the average value of an array of value just inside a Twig template when using Symfony2?
I tried something like the following code but it doesn't work
{% set sumratings = 0 %}
{% set count = 0 %}
{% for rating in article.ratings %}
{% set sumratings = sumratings + rating.value %}
{% endfor %}
AVG: {{ sumratings / article.ratings.count }}
Any idea?
I think this should do it
{% set sumratings = 0 %}
{% for rating in article.ratings %}
{% set sumratings = sumratings + rating.value %}
{% endfor %}
AVG: {{ sumratings / article.ratings|length }}
Assuming rating.value is an integer
There is no built-in way to do that. Creating your own twig extension that can do it is simple! You need to create a filter, which is very well doumented in the cookbook: How to write a custom Twig Extension.
The answer is correct but horrible to use, right ?
It's simple:
function arrayAverage($array)
{
if (!is_array($array)) return false;
if (!count($array)) return 0;
$avg=0;
foreach ($array as $num) $avg+=$num;
$avg/=count($array);
return $avg;
}
$twig->addFilter('arrayAverage', new Twig_Filter_Function('arrayAverage'));
Now you can just do like this in your twig templates:
{{ somearray | arrayAverage }}
{{ somearray | arrayAverage | number_format }}
In modern Twig versions, there's a reduce() filter that can simplify this work, a bit:
{% set myValues = [1, 2, 3, 4, 5] %}
{% set average = (myValues | reduce((sum, val) => sum + val, 0)) / (myValues | length) %}
{{ average }} {# 3 #}
See this fiddle!

Categories