How to iterate over many properties in Twig loop? - php

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.

Related

Symfony Undefined variable (contexterror exception) when array empty in controller

Here is the thing. A trainer can speak zero to many languages. I want to display the languages for one or several trainers on a page. Note that I'm working on a code I didn't start from scratch.
In my controller :
foreach ($trainers as $trainer){
$trainer_entity = $doctrine->getRepository('AppBundle:Trainer')->find($trainer["id"]);
$listLangues = $this->getDoctrine()
->getManager()
->getRepository('AppBundle:TrainerLangues')
->findByTrainer_id($trainer["id"]);
}
return [
'user' => $user,
'listLangues' => $listLangues,
'trainers' => $trainers,
];
In my twig file:
{% if listLangues %}
<b>Langues</b> :
{% for l in listLangues %}
{{ l.langue.name }}
{% endfor %}
{% endif %}
This works when the person is linked to languages, so listLangues not empty.
Otherwise I get
Notice: Undefined variable: listLangues 500 Internal Server Error -
ContextErrorException
I tried {% if listLangues is defined %} as well, but the system seems to block in the controller. It never happened to me before.
note that initializing $listLangues only leads to no display on the twig page.
Any idea?
what do you really want to display in your view?
Here is a simple tips, you could try in your controller:
/***/
public function yourAction()
{
return $this->render('your-template.html.twig', [
'user' => $this->getUser(),
'trainers' => $this->getDoctrine()->getRepository(Trainer::class)->findAll()
]);
}
/***/
Then, in your view (basic implementation)
{% for trainer in trainers %}
<h4>{{ trainer.name }}</h4>
{% if trainer.languages %}
<ul>
{% for language in trainer.languages %}
<li>{{ language.name }}</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
Hope, it will help you.
If your array $trainers is empty, the foreach nevers loops so the $listLangues var is never defined.
Try declaring it as an empty array BEFORE the foreach :
$listLangues = array();
You need to instantiate $listLangues because if there isn't trainers the variable can't exist and code is broke.
Try to add this before your foreach:
$listLangues = null;
Or instantiate It with a value or what you want but declare It before your foreach to be sure that exists

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

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

Avoid repeating the same query in controllers for parent template

I am trying to make a messaging system and I'm facing a small problem. I have a bigger template that displays my menu and my content. The menu includes the number of new messages, and the content can be any page(compose a new message, inbox, sent).
The problem is that I have to render each of the small templates by passing the number of new received messages to each of them, calling the doctrine each time and repeating code. Is there any way to send the number only to the parent template?
Here are my templates:
This is the parent containing the newmsg variable that gives me problems.
{% extends "::base.html.twig" %}
{% block body %}
inbox : {{ newmsg }}
sent
compose
{% endblock body %}
Here is an example of child template:
{% block body %}
{{ parent() }}
{% if messageList %}
{% for message in messageList %}
<li>title = {{ message.title|e }}</li>
<li>cont= {{ message.content|e }}</li>
<li>data= {{ message.date|date('d-m-Y H:m:s') }}</li>
<li>sender= {{ message.sender|e }}</li>
<hr>
{% endfor %}
{% else %}
<div>no messages</div>
{% endif %}
{% endblock body %}
The problem is that each child template is asking me for the newmsg variable
$messages = $this->getDoctrine()->getRepository('MedAppCrudBundle:Message');
$newMessagesNo = count($messages->findBy(array('seen' => '0', 'receiver' => $this->getUser())));
return $this->render(
'MedAppCrudBundle:UserBackend\Message:new.html.twig',
array(
'form' => $form->createView(),
'newmsg' => $newMessagesNo,
)
);
And I have to write this in every single controller. Any way I can shorten this problem?
You could implement a service that gives back the newmsg value and call it on your parent template. Then it would not be necessary to pass the variable.
You can add a service in your bundle's services.yml with something like:
services:
newmessages:
class: Full\Class\Name\NewMessagesService
arguments: ["#doctrine.orm.entity_manager"]
Then, implement the Full\Class\Name\NewMessagesService class. Keep in mind that this class will need a constructor that receives an EntityManager argument. Something like:
<?php
namespace Full\Class\Name;
class NewMessagesService{
private $entityManager;
public function __construct($entityManager){
$this->entityManager = $entityManager;
}
public function methodToCalculate(){
//Perform calculation and return result
}
}
Then, in your parent template, replace {{newmsg} with:
{{ newmessages.methodToCalculate() }}

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

Categories