How do I output data from a related object in a Twig template? - symfony

I'm still very new to Symfony2 so please go easy on me. I'm trying to loop through a table of flights (for a flight ticket booking system), which have several related fields, such as airline, and airport. I'm using the following method in my custom repository:
public function getAllFlights($limit = 100)
{
$dql = 'SELECT f FROM Flightcase\BookingBundle\Entity\Flight f';
$query = $this->getEntityManager()->createQuery($dql);
$query->setMaxResults($limit);
return $query->getResult();
}
and the getAllFlights() is being passed to my Twig template like so:
$flights = $em->getRepository('FlightcaseBookingBundle:Flight')->getAllFlights();
return $this->render('FlightcaseBookingBundle:Flight:list.html.twig', array('flights' => $flights));
And the Twig template is simply looping through the items inside the $flights collection like this:
{% for flight in flights %}
<tr>
<td>{{ flight.airline }}</td>
<td>{{ flight.origin }}</td>
<td>{{ flight.destination }}</td>
<td>{{ flight.dateFrom }}</td>
<td>{{ flight.timeFrom }}</td>
<td>{{ flight.dateTo }}</td>
<td>{{ flight.timeTo }}</td>
</tr>
{% endfor %}
But I get a ugly, cryptic exception telling me "Object of class Proxies\FlightcaseBookingBundleEntityAirlineProxy could not be converted to string" which leads me to believe I need to fetch a specific property inside the Airline object such as the IATA code to output as string. But how can I access the $airline->getIataCode() inside the Twig template? Or is there a way in my repository to convert the related objects into strings?

I am assuming that Airline is a separate entity, which has an association to the Flight entity in Doctrine. Something like:
class Airline
{
private $id;
private $name;
private $flights;
...
}
Is that correct? If so, then that's the reason you're seeing that specific error. You're giving Twig an object, and telling it to print it out... but what does that mean, exactly?
Let's assume that your class looks like the above, and you're just trying to print out the name of the Airline.
You could do one of two things:
First, you could give your object a toString() method:
class Airline
{
public function toString()
{
return $this->getName();
}
}
Alternatively, you can give Twig something scalar to work with: Replace {{ flight.airline }} with {{ flight.airline.name }}.
Edit:
Just saw that your Airline object has a property called $IataCode. In that case, you'd render it in Twig using {{ flight.airline.IataCode }}.

Related

Flatten array structure in Doctrine QueryBuilder JOIN query in Symfony 3.4

I have the following custom query in my PersonRepository in my Symfony 3 application. It joins the Person entity with the Log entity where I can grab the created time of the Person.
public function findAllPlayers()
{
$qb = $this->getEntityManager()->createQueryBuilder('p');
$qb
->select('p AS playerInfo', 'al.time AS createdTime')
->from('AppBundle:Player', 'p')
->join('AppBundle:Log', 'al', Expr\Join::WITH, $qb->expr()->eq('al.player', 'p.id'))
->where(
$qb->expr()->andX(
$qb->expr()->eq('p.type', ':type'),
$qb->expr()->eq('al.type', ':al_type')
)
)
->setParameter('type', 'midfielder')
->setParameter('al_type', 'log.player.created');
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
}
When I feed this through to my Twig template i'm left with this awkward index which i'd like to eliminate if possible.
{% for index, player in players %}
<tr>
<td>{{ player.playerInfo.playerShortname }}</td>
</tr>
{% endfor %}
Is there a way I can "flatten", for want of a better word, the structure to remove the superfluous index from the returned structure. It works, but it's a bit messy and the index is not needed or used anywhere.
Your twig template doesn't use index variable so you can feel free to remove it:
{% for player in players %}
<tr>
<td>{{ player.playerInfo.playerShortname }}</td>
</tr>
{% endfor %}
If you are looking solution for rendering multiple users:-
You can use the following snippet. It is not mandatory to use index in the for loop. You can refer the first example available in https://twig.symfony.com/doc/2.x/tags/for.html
{% for player in players %}
<tr>
<td>{{ player.playerInfo.playerShortname }}</td>
</tr>
{% endfor %}
If you are looking for a solution to render just one result:-
There is a method - "getOneOrNullResult". But, it throws an exception if the no. of rows are more than one. So, apply limit = 1 in the DQL and use the method "getOneOrNullResult" instead of "getResult"
To limit the no. of limits, you can use the method - "setMaxResults()"
Your method would like below
public function findAllPlayers()
{
$qb = $this->getEntityManager()->createQueryBuilder('p');
$qb
->select('p AS playerInfo', 'al.time AS createdTime')
->from('AppBundle:Player', 'p')
->join('AppBundle:Log', 'al', Expr\Join::WITH, $qb->expr()->eq('al.player', 'p.id'))
->where(
$qb->expr()->andX(
$qb->expr()->eq('p.type', ':type'),
$qb->expr()->eq('al.type', ':al_type')
)
)
->setParameter('type', 'midfielder')
->setParameter('al_type', 'log.player.created');
return $qb->getQuery()->setMaxResults(1)->getOneOrNullResult(Query::HYDRATE_ARRAY);
}
Note:-
But, I don't feel joining log to get the created_time is a ideal solution since that can cause serious performance issues as the log table grows. It is ideal to store the created_time in the table if there is a business around it.

Symfony Twig : get Object property with a variable property

Symfony 3.0 :
In my project, I have many entities which contain more than 50 fields, so for the twig which shows every entity, I decided to automate the display of the 50 fields by a simple loop.
First problem: how to get entity's all fields names, I resolved this by creating a custom twig filter:
<?php
// src/HomeBundle/Twig/HomeExtension.php
namespace HomeBundle\Twig;
class HomeExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('object_keys', array($this, 'getObjectKeys')),
);
}
public function getObjectKeys($object)
{
//Instantiate the reflection object
$reflector = new \ReflectionClass( get_class($object) );
//Now get all the properties from class A in to $properties array
$properties = $reflector->getProperties();
$result=array();
foreach ($properties as $property)
$result[] = $property->getName();
return $result;
}
public function getName()
{
return 'app_extension';
}
}
?>
The second problem which generates the error right now is: how to access object properties within a loop:
{% for property in article|object_keys%}
<tr>
<th>
{{property|capitalize}}
{# that's work clean #}
</th>
<td>
{{ attribute(article,property) }}
{# that's generate the error #}
</td>
</tr>
{% endfor %}
The error :
An exception has been thrown during the rendering of a template
("Notice: Array to string conversion"). 500 Internal Server Error -
Twig_Error_Runtime
Finally, the error is fixed on the getObjectKeys method of the filter,
so when it returns an array that I create manually it works:
return array("reference","libelle");
But, when I send an array created within a loop => Error.
I dumped the two arrays in the twig, they were equivalents, but the second still generating an error.
Most likely one of your properties is returning an array rather than a simple string, integer, .... A solution here could be to store the value in a variable and check whether the stored value is an array. Depending on that check do something with the value or otherwise just output the variable
{% for property in article|object_keys%}
<tr>
<th>
{{property|capitalize}}
</th>
<td>
{% set value = attribute(article,property) %}
{% if value is iterable %}{# test if value is an array #}
{{ value | join(', ') }}
{% else %}
{{ value }}
{% endif %}
</td>
</tr>
{% endfor%}

Symfony2, createQueryBuilder count one to many relationship

I have 2 entities linked by a one to many relation.
Recruitment and Candidat
You may have many candidats for one recruitment.
I want to list all recruitment and count how many candidat each recruitment has.
I use the recruitment repository and put the code:
public function myFindAllRecruitment()
{
$qb = $this->createQueryBuilder('r');
$qb->select('r');
$qb->Join('r.candidat', 'c');
$qb->addSelect("COUNT(c.id) as candidatCount");
$qb->groupBy('r.id');
$qb->orderBy('r.id', 'DESC');
return $qb
->getQuery()
->getResult()
;
}
In my RecruitmentController I have:
$listRecruitment = $repository->myFindAllRecruitment();
In my TWIG view something like:
{% for recruitment in listRecruitment %}
<tr>
{#(this is line 48)#}<td>{{ recruitment.id }}</td>
<td>{{ recruitment.titleFr }}</td>
<td>{{ recruitment.locationFr }}</td>......
And I get this error:
"Key "id" for array with keys "0, candidatCount" does not exist in MyBundle:Recruitment:index.html.twig at line 48"
If someone knows what wrong with my query it will be nice.
Thank you
So you have this Candidat entity like this
......
/**
* #ORM\ManyToOne(targetEntity="Recruitment", inversedBy="candidates")
*/
$recruitment;
....
Then your query should look like this:
public function myFindAllRecruitment()
{
$qb = $this->createQueryBuilder('r');
$qb->select('r');
->addSelect('(SELECT count(c) FROM PATHTO\Bundle\Entity\Candidat as c WHERE c.rectuitment = r.id group by c.rectuitment) as count)');
->orderBy('r.id', 'DESC');
return $qb->getQuery()->getResult();
}
you will get output like this:
[
0 => [
'rectuitment' => ...Object,
'count'=> ...,
...
]
Ok, thank you, I found the solution.
It was in the TWIG render:
Objects were accessible with [0] and [1] or ['candidatCount'] for the the count
Code exemple:
{% for recruitment in listRecruitment %}
<tr>
<td>{{ recruitment[0].id }}</td>
<td>{{ recruitment[0].titleFr }}</td>
<td>{{ recruitment[0].locationFr }}</td>
<td>{{ recruitment[0].typePoste }}</td>
<td>{{ recruitment[0].dateins|date('Y-m-d') | localizeddate('full', 'none') }}</td>
<td>
{{ recruitment['candidatCount'] }}........
And the query was good.
Thank you

Return Data via Meteor Method within a Loop

I'm looping through a list of credit card transactions and need to call out to the credit card processor to get the current status. I tried to use a helper that calls a method, but it doesn't get rendered on the template. If I log to the console, I can see that the results are properly being returned, though.
<tbody>
{{#each donations}}
<tr>
<td>{{ transactionId }} {{ status transactionId }}</td>
<td>{{ donorName donor }}</td>
<td>{{ donorEmail donor }}</td>
<td>{{ formatMoney amount }}</td>
<td>{{ createdAt }}</td>
</tr>
{{/each}}
</tbody>
Pulling the data:
donations: function() {
return Donations.find({
type: 'Credit Card',
createdAt: { $gte: new Date('Jan 1, 2015'), $lt: new Date('Jun 30, 2015') }
});
},
The status helper:
status: function(transactionId) {
var transaction = Meteor.call('getTransactionInfo', transactionId, function(error, result) {
console.log(result);
return result.status;
});
}
And the method:
getTransactionInfo: function(transactionId) {
var transaction = Meteor.wrapAsync(gateway.transaction.find, gateway.transaction);
var response = transaction(transactionId);
return response;
},
I found several questions that talked about putting the data in a ReactiveVar or Session variable, but I don't know how I could use that, considering that I am returning the info for every instance of the loop.
You can create a new template donation. Each row in the table will be its own donation instance, like this:
<tbody>
{{#each donations}}
{{> donation }}
{{/each}}
</tbody>
Now inside the onCreated for the donation template, create a ReactiveVar. Then immediately call the method, and in the method's callback, set the ReactiveVar. Since each row is its own template instance, each row will end up with its own ReactiveVar.
Template.donation.onCreated(function () {
var status = new ReactiveVar(null);
this.status = status;
Meteor.call('getTransactionInfo', this.data.transactionId, function (err, result) {
if (err) { /* handle it */ }
status.set(result);
});
});
Template.donation.helpers({
status: function () { return Template.instance().status.get(); },
/* rest of the helpers */
});
Now when the template is first created, the method call is still ongoing, so status.get() returns null. We have to test for this and display a loading spinner. After the method call completes, status.get() will be set to the result, so the spinner will be replaced with the value.
<tr>
<td>
{{ transactionId }}
{{#if status}}
{{ status }}
{{else}}
loading... <!-- or display a spinner -->
{{/if}}
</td>
<td>{{ donorName donor }}</td>
<td>{{ donorEmail donor }}</td>
<td>{{ formatMoney amount }}</td>
<td>{{ createdAt }}</td>
</tr>
From a security pov you've just exposed a lookup to your bank's systems from your (untrusted) clients. Anyone can run Meteor.call('getTransactionInfo', this.data.transactionId...) from their browser and that will end up hitting your bank. Great DDOS vector. Even absent malicious users these computations will end up running repeatedly for every transaction.
Better approach: run a cron job on your server that periodically checks the status of transactions (until they have cleared and no longer need checking) and put those stati into your donation collection.

How to implement two tables intersection on Doctrine2 Symfony2

Im pretty new on all this stuff so please excuse my foolness.
I have two tables, clients and orders.
I need to show on a template all clients with their total orders amount for each.
According to MCV model, I just want the controller to call a method of the Clients repository that returns an array with all the data ready for sending it to the template.
But I having problems with implementing this.
So in summary I have:
Entities:
Clients.php: with client.id
Orders.php: with a manyToOne relatioonship to clients, and a amount property.
DefaultController.php
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$clientsWithTotals= $em->getRepository('ACMEAppBundle:Client')->getClientsWithTotals();
return $this->render('ACMEAppBundle:Default:index.html.twig',$clientsWithTotals);
}
ClientRepository.php:
class ClientRepositoryextends EntityRepository
{
public function getClientsWithTotals()
{
???
}
}
index.html.twig
{% extends '::base.html.twig' %}
{% block content %}
<table>
<thead>
<tr>
<th>Id</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{% for entity in entities %}
<tr>
<td><{{ entity.id }}</td>
<td>{{ entity.total }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
Many thanks indeed
First off the template is using a variable which hasn't been defined (entities):
return $this->render(
'ACMEAppBundle:Default:index.html.twig',
array(
'entities' => $clientsWithTotals
)
);
Implementing the Repository:
class ClientRepository extends EntityRepository
{
public function getClientsWithTotals()
{
$this
->createQueryBuilder('client')
->join('client.orders', 'order');
}
}
As long as you have the oneToMany association correctly defined from Client to Orders and it's on the $client->orders property then the join will know that it has to filter out only the orders associated with a particular user.
Implementing the totals virtual property:
Your Client object will have no way of knowing what the totals are unless you do one of the following:
select the value from the DB using an aggregation function like SUM(orders.total) AS clientTotals.
select the orders associated with a client from the DB (Doctrine2 will hydrate them into $client->orders) and then using a foreach loop to sum up all the values.
As described here.
Ex. for #1:
You can construct a DQL query such as:
SELECT
client,
SUM(orders.total) as clientTotal
FROM ACMEAppBundle:Client AS client
JOIN client.orders AS orders;
The above query would normally return an array containing a all of the rows in the clients table in the following format:
array(
'client' => $client // an ACMEAppBundle:Client entity object
'clientTotals' => $clientTotals // an integer value representing the aggregate sum
);
Implementing a custom hydrator will allow you to decide what to do with the raw array data which is returned by the DB query.
Ex. for #2:
class Client
{
...
public function getTotals()
{
$this->totals = 0;
foreach ($this->getOrders as $order) {
$this->totals += $order->getTotal();
}
return $clientTotals;
}
...
}

Resources