Symfony2, createQueryBuilder count one to many relationship - symfony

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

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 Native Query

I work on a project under symfony, when I want to calculate the average product by month and year (date) and as doctrine doesn't include the Month or YEAR functions I used native sql but it didn't show the results, it return empty array.
If someone can help me,Thank you.
Repository:`
public function MonthEfficience()
{
$rsm = new ResultSetMappingBuilder($this->getEntityManager());
$rsm->addRootEntityFromClassMetadata('GP\PlatformBundle\Entity\Efficience', 'e');
$rsm->addJoinedEntityFromClassMetadata('GP\PlatformBundle\Entity\collectif', 'c', 'e', 'collectif', array('id' => 'collectif_id'));
$sql = 'SELECT (AVG(e.produit_real/e.produit_plan)*100) as moyenne,
MONTH(e.date) as mois, YEAR(e.date) as annee FROM efficience e, collectif c
where e.collectif_id=c.id group by mois, annee';
$query = $this->_em->createNativeQuery($sql, $rsm);
$resultats = $query->getResult();
return $resultats;
}
Controller:
public function effmonthAction()
{
$em = $this->getDoctrine()->getManager()
->getRepository('GPPlatformBundle:Efficience');
$efficiences = $em->MonthEfficience();
return $this->render('GPPlatformBundle:App:effmonth.html.twig',
array('efficiences'=>$efficiences));
}
Twig :
<table id="example" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>Annee</th>
<th>Mois</th>
<th>Moyenne</th>
</tr>
</thead>
<tbody>
{% for efficiences in efficiences %}
<tr>
<td>{{ efficiences.produitplan }}</td>
<td>{{ efficiences.produitreal }}</td>
<td>{{ efficiences.produitplan }}</td>
</tr>
{% endfor %}
</tbody>
</table>
Resultats:
empty array

Symfony2 Doctrine+Twig query taking too long

I'm currently building a query to return a set of records between desired dates as it follows:
public function findBetweenDates(\Datetime $date1,\Datetime $date2)
{
$date1=$date1->setTime(07,00,00);
date_modify($date2,'+1 day');
$date2->setTime(06,59,00);
$qb = $this->getEntityManager()->createQueryBuilder()
->select('e')
->from("AppBundle:Movimento","e")
->andWhere('e.pesagem1 BETWEEN :from AND :to')
->setParameter('from', $date1 )
->setParameter('to', $date2)
->orderBy('e.id','DESC')
;
$result = $qb->getQuery()->getResult();
return $result;
}
the class Movimento has some ManyToOne connections as shown below:
class Movimento
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Service")
* #ORM\JoinColumn(name="service", referencedColumnName="id")
**/
private $service;
When i get the records and render them in twig:
{% for item in items %}
<tr>
<td>{{ item.id }} </td>
<td>{{ item.service.name }}</td>
//#MORE CODE BELOW //
by calling servico.name from another entity i get tons of non wanted queries as a result to display the name of the service instead of its id.
We are talking about something in the 6k range of records in every response.
I would like some help, if it's possible to optimize this query using the my query builder or should i remake the whole query more of a "SQL" example:
Select a.name, b.id
From service as a, movimento as b
Between bla bla bla
Any Help/suggestions are greatly appreciated.
EDIT 1
i changed my query builder after reading this post Symfony 2/Doctrine: How to lower the num of queries without losing the benefit of ORM?
I did reduce 175 queries to a single one
$qb = $this->createQueryBuilder('e')
->addSelect('service')->join('e.service','service')
->addSelect('motorista')->join('e.motorista','motorista')
->addSelect('residuo')->join('e.residuo','residuo')
// ->from("AppBundle:Movimento","e")
->andWhere('e.pesagem1 BETWEEN :from AND :to')
->setParameter('from', $date1 )
->setParameter('to', $date2)
->orderBy('e.id','DESC')
But still the page is taking around 8 seconds to load (its 6900 records) and after checking performance the response time for my new query is 177.79 ms, but my twig+ controller is taking the remaining 7.x seconds as it shows the pic
my controller is something really simple
public function getMovimentosAction(Request $request)
{
$startDate = $request->request->get('startDate');
$endDate = $request->request->get('endDate');
if (empty($startDate))
$startDate = date("Y-m-d") ;
if (empty($endDate))
$endDate = date("Y-m-d");
$em=$this->getDoctrine()->getRepository('AppBundle:Movimento');
$dados=$em->findBetweenDates(new \DateTime($startDate),new \DateTime($endDate));
return $this->render('AppBundle:Movimentos:logtable-movimento.html.twig', array(
'items' => $dados
));
}
and my twig just iterates over the rows and displays them on a table as i gave a partial example above.
Any help/suggestions would be greatly appreciated.
EDIT2
My view that is passed by ajax to be rendered as datatable.js
<table id="example" class="table table-striped table-bordered table-hover" cellspacing="0" width="100%">
<thead class="dataTableHeader">
<tr>
<th>Talão</th>
<th>Nº Talão</th>
<th>Motorista</th>
<th>Residuo</th>
<th>Serviço</th>
<th>Matricula</th>
<th>1º Pesagem</th>
<th>Peso Liquido</th>
<th>Fluxo</th>
<th>Circuito</th>
<th>Verificado</th>
<th></th>
</tr>
</thead>
<tfoot class="dataTableHeader">
<tr>
<th>Talão</th>
<th>Nº Talão</th>
<th>Motorista</th>
<th>Residuo</th>
<th>Serviço</th>
<th>Matricula</th>
<th>1º Pesagem</th>
<th>Liquido</th>
<th>Fluxo</th>
<th>Circuito</th>
<th>Verificado</th>
<th></th>
</tr>
</tfoot>
<tbody>
{% for item in items %}
<tr>
<td align="center"><a href="{{ path("_movimento_generate_pdf",{ id: item.id }) }}"> <i class="fa fa-print fa-2x" aria-hidden="true"></i>
</a></td>
<td>{{ item.id }} <a><i class="fa fa-eye" title="Visualizar Movimento" aria-hidden="true"></i></a>
</td>
<td>{{ item.motorista.idFuncionario }} - {{ item.motorista.nome }}</td>
<td>{{ item.residuo.nome }}</td>
<td>{{ item.servico.nome }}</td>
<td>{{ item.matricula }}</td>
<td>{{ item.pesagem1|date('Y-m-d h:m') }}</td>
<td>{{ item.liquido }} kg</td>
<td>{% if item.tipoMovimento == 1 %} Entrada {% else %} Saida {% endif %}</td>
<td>{{ item.circuito.code | default(" ") }}</td>
<td class="text-center">{% if item.enable==1 %}
<span style="color: transparent"> </span>
<i class="fa fa-circle" aria-hidden="true" style="color: green"></i>
{% else %}
<i class="fa fa-times" aria-hidden="true" style="color: red;"></i>
{% endif %}
</td>
<td class="text-center">
<a class="btn btn-default" href="{{ path('_movimentos_edit',{ 'id' : item.id}) }}">
<i class="fa fa-cog" title="Editar" aria-hidden="true"></i>
<span class="sr-only">Settings</span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
and in my html
$("#submitButtonQuery").click(function(event){
event.preventDefault();
var l = Ladda.create(this);
l.toggle();
$.post( "/movimentos/getList",
$( "#formAjaxify" ).serialize())
.done(function(data)
{
$('#example').remove();
$("#tabelaLog").html(data);
oTable=$('#example').DataTable(
{
"scrollX": true,
responsive: true,
"language": {
"url": "http://cdn.datatables.net/plug-ins/1.10.11/i18n/Portuguese.json"
}
}
);
oTable.order( [ 0, 'desc' ] )
.draw();
})
.always(function(){
l.toggle()})
;
});
As long as you have a connection between 'Movimento' and 'Service' then for each 'movimento' that you get as a result a 'service' will be serialized together. This means that if you have a query that returns 100 'movimento' then together with it all 'service' objects (100) will be required to be fetched.
If you don't want to have the Service as an object in each item (AKA item.service.blahblah) then you need to have a more direct query.
if you do it with query builder then you will need something like:
$repository = $this->getDoctrine()
->getRepository('YourownBundle:Movimento'); //the main repo from which to get data
$query = $repository->createQueryBuilder('m') // query builder on repo
->join('m.service', 's') // join the second object to select from
->select('m.id') // select everything from m objet
->addSelect('s.name') // select everything from service (s) object
->where('e.pesagem1 BETWEEN :from AND :to')
->setParameter('from', $date1 )
->setParameter('to', $date2)
->orderBy('e.id','DESC')
the rest of your code should be as you have it... but then you don't have a serialized object but only the selects that you make (eg. m.id, s.name)

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 do I output data from a related object in a Twig template?

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

Resources