Twig and Doctrine- Count each related entity and display in Twig loop - symfony

I have a one to many relationship in doctrine.I want to count each related field and display them in Twig for loop
so far
A Vp is related to Voters.Vp has many Voters and Voters has one Vp
I want to count each related Voters per Vp
public function getAllVp()
{
return $this
->createQueryBuilder('v')
->select('vp.id,COUNT(v.id) as num')
->from('Voters', 'v')
->join('v.Vp', 'vp')
->orderBy('v.id', 'ASC')
->getQuery()
->getResult()
;
}
I want this in Twig like
{% for vp in vps %}
{{ vp.firstname }}
{{ vp.num }}//number of voters
{% endfor %}
controller
$vice_president = $em->getRepository('Bundle:Vp')->getAllVp();
return $this->render('Bundle:Vp:all_vp.html.twig', array(
'vps' => $vice_president,
));
doctrine
fields:
firstname:
type: string
length: 255
lastname:
type: string
length: 255
photo:
type: string
length: 255
oneToMany:
voters:
targetEntity: Voters
mappedBy: vp
I got this error
[Semantical Error] line 0, col 94 near 'vp, Voters v': Error: Class Project\Bundle\DuterteBundle\Entity\Vp has no association named Vp
How to correctly achieve this in Doctrine?
Update
voters.orm.yml
manyToOne:
vp:
targetEntity: Vp
cascade: { }
mappedBy: null
inversedBy: voters
joinColumn:
name: vp_id
referencedColumnName: id
orphanRemoval: false
I can achieved this by simply calling the related 'voters' and add a filter in Twig.But my intention is to count the data in doctrine, reuse it in other templates or convert it to json for the future,e.g in Angular JS
{% if vp.voters|length > 0 %}
<tr {% if loop.index is odd %}class="color"{% endif %}>
<td>{{ vp.id }}</td>
<td>{{ vp.getFullName() }}</td>
<td>{{ vp.voters|length|number_format }}</td>
</tr>
{% endif %}
Above is a working code but I want to do the count in Doctrine , not in template
Expected result
id fullname counts
1 George Bush 45
2 ali gail 1999
4 Mae Young 45
......

First of all, you can remove mappedBy: null in your Voter mapping.
PHP oriented :
Ok you can try this PHP solution to add a new method in your entity Vp like :
public function getVotersCount(){
return count($this->voters);
}
And in your twig view you can do :
{{ vp.getVotersCount() }}
Doctrine oriented : (http://docs.doctrine-project.org/en/latest/reference/events.html#lifecycle-events)
In your Vp Entity orm mapping :
fields:
firstname:
type: string
length: 255
lastname:
type: string
length: 255
photo:
type: string
length: 255
oneToMany:
voters:
targetEntity: Voters
mappedBy: vp
lifecycleCallbacks:
postLoad: [ countVotersOnPostLoad ]
And also a new attribute, a getter and countVoters method :
protected $votersCount;
public function getVotersCount(){
return $this->votersCount;
}
public function countVotersOnPostLoad ()
{
$this->votersCount = count($this->voters);
}
And in your view, simply do :
{{ vp.votersCount }}

My work around for this is to create a service.
<?php
namespace Project\Bundle\DutBundle\Twig;
class AllVpExtension extends \Twig_Extension
{
protected $em;
public function __construct($em)
{
this->em = $em;
}
public function getFunctions()
{
return array(
//this is the name of the function you will use in twig
new \Twig_SimpleFunction('number_votes_vp', array($this, 'b'))
);
}
public function getName()
{
//return 'number_employees';
return 'vp_app_extension';
}
public function b($id)
{
$qb=$this->em->createQueryBuilder();
$qb->select('count(v.id)')
->from('DutBundle:Voters','v')
->join('v.vp','c')
->where('c.id = :x')
->setParameter('x',$id);
$count = $qb->getQuery()->getSingleScalarResult();
return $count;
}
}
Now in order to count each vp's related voters, I can call a service and send the result to twig
public function all_vpAction()
{
$em = $this->getDoctrine()->getManager();
$vice_president = $em->getRepository('DutBundle:Vp')->findAll();
//communicate to service container
$data = $this->container->get('duterte.twig.vp_app_extension');
$datas = array();
foreach ($vice_president as $value) {
$datas[] = array('id' => $value->getId(),'firstname' => $value->getFirstname() . ' ' . $value->getLastname(),'numbers' => (int)$data->b($value->getId()));
}
$vice = $datas;
return $this->render('DutBundle:Vp:all_vp.html.twig', array(
'vps' => $vice,
));
//or we can wrap this in json
$serializer = $this->container->get('jms_serializer');
$jsonContent= $serializer->serialize($vice,'json');
return $jsonContent;
}
With this set up, I can wrap this into json and and using custom twig filter, I can display data sorted either in Angular or in plain Twig template or both
By the way, my view
{% extends '::base.html.twig' %}
{% block body %}
{% block stylesheets %}
{{ parent() }}
<style type="text/css">
#img-responsive{
height: 320px;
/*width: 300px;*/
}
</style>
{% endblock %}
<div class="section-heading">
<h2>Best Tandem Of the Day</h2>
</div>
<div class="row">
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="/img/dut.jpg" id="img-responsive">
<div class="caption">
<h3>President</h3>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="/img/unknown.jpg" id="img-responsive">
<div class="caption">
<h3>Vice-President</h3>
</div>
</div>
</div>
</div>
<hr />
<div ng-app="myApp" ng-controller="customersCtrl">
Search Here: <input type="text" placeholder="search" ng-model="searchMe"/><br />
<table class="table">
//names//
<thead>
<tr>
<th>Full Name</th>
<th>Middlename</th>
<th>Lastname</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="x in names">
<td>//x.id//</td>
<td>//x.firstname//</td>
<td>//x.numbers//</td>
</tr>
</tbody>
</table>
</div>
<div class="table-responsive">
<table class="table table-hover table-bordered table-condensed" id="table1">
<thead>
<tr>
<th>#</th>
<th>Bet</th>
<th>Votes</th>
<!--th>Photo</th-->
</tr>
</thead>
<tbody>
{% for v in vps | sortbyfield('numbers') %}
{% if v.numbers > 0 %}
<tr>
<td>{{ v.id }}</td>
<td>{{ v.firstname }}</td>
<td>{{ v.numbers }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script src="//code.angularjs.org/1.4.8/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.config(function($interpolateProvider) {
$interpolateProvider.startSymbol('//');
$interpolateProvider.endSymbol('//');
});
app.controller('customersCtrl',['$scope','$http',function($scope, $http) {
$http.get("{{ path('vp_president') }}")
.success(function (response) {
$scope.names= JSON.parse(response);
});
</script>
{% endblock %}

Related

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)

Why function relates to code that is not in function?

I have the following controller:
<?php
namespace Dev\TaskBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Session\Session;
use Dev\TaskBundle\Entity\User;
class UserController extends Controller
{
/**
* Redirects to directory where you can input new password
*
* #Route("/{id}", name="user_update")
*/
public function userUpdatePanel($id)
{
$user = $this->getDoctrine()->getRepository('DevTaskBundle:User')->find($id);
return $this->render('DevTaskBundle:User:userUpdate.html.twig', array('user' => $user));
}
/**
* Updates password of the user
*
* #Route("/updatePassword", name="updatePass_action")
*/
public function passUpdateAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$id = $request->get('id');
$user = new User();
$user = $em->getRepository('DevTaskBundle:User')->find($id);
$password = $request->get('passInput');
$user->setPassword($password);
$em->flush();
$users = $this->getDoctrine()->getRepository('DevTaskBundle:User')->findAll();
return $this->render('DevTaskBundle:User:accounts.html.twig', array('users' => $users));
}
}
Here is form from userUpdate.html.twig
<form action="{{ path('updatePass_action') }}" method="POST">
<div>
Username: {{ user.username }}
</div>
<input type="hidden" name="id" value="{{user.id}}">
<div class="form-group">
<label for="passInput">Password:</label>
<input type="text" class="form-control" id="passInput" name="passInput" value="{{ user.password }}">
</div>
<button type="submit" class="btn btn-default">Change</button>
</form>
and when I proceed to change the password I receive such error
Impossible to access an attribute ("username") on a null variable in DevTaskBundle:User:userUpdate.html.twig at line 23
After changing the password I want to render accounts.html.twig so why there is the error about userUpdate? What is wrong here?
Part of accounts.html.twig with twig scripts:
<table class="table table-bordered">
<thead>
<tr>
<th>Login</th>
<th>Hasło</th>
<th>Narzędzia</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>
{{ user.username }}
</td>
<td>
{{ user.password }}
</td>
<td>
<a href="{{ path('user_update', {'id': user.id}) }}" >
<span class="glyphicon glyphicon-pencil linkDeco" ></span>
</a>
{% if user.status == 1 %}
<a href="{{ path('deleteAction', {'name': 'User' ,'direction': 'account_panel' , 'id': user.id}) }}" >
<span class="glyphicon glyphicon-trash linkDeco"></span>
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
EDIT
problem must be related to userUpdate.html.twig because when I deleted
<div>
Username: {{ user.username }}
</div>
I have the error
Impossible to access an attribute ("id") on a null variable in DevTaskBundle:User:userUpdate.html.twig at line 24
Any idea why is that?
Doctrine find method return array of objects, use findOne instead of find when you expecting only one result.
$user = $this->getDoctrine()->getRepository('DevTaskBundle:User')->findOneById($id);

Display image stored in BLOB database in symfony

I load my image (blob data ) in my GETer Entity
When I just return ($this->foto) in my GETer I see :Resource id #284 on the screen
When I change my GETer like this : return stream_get_contents($this->foto);
I see these : ���JFIF��� ( ,,,,,,,, ( and more )
In my Controller a call the index.html.twig to show all my entities
/**
* Lists all Producten entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('CustomCMSBundle:Producten')->findAll();
return $this->render('CustomCMSBundle:Producten:index.html.twig', array(
'entities' => $entities,
));
}
Now in my views ( index.html.twig ) I like to show the picture
{% for entity in entities %}
<tr>
<td>
<img src="{{ entity.foto}}" alt="" width="80" height="80" />
</td>
<td>
{{ entity.foto }}
</td>
<td>
<ul>
<li>
show
</li>
<li>
edit
</li>
</ul>
</td>
</tr>
{% endfor %}
But I don't see the picture ?
Can anyone help me?
You are using <img src="(raw image)"> instead of <img src="(image's url)">
A quick solution is to encode your image in base64 and embed it.
Controller
$images = array();
foreach ($entities as $key => $entity) {
$images[$key] = base64_encode(stream_get_contents($entity->getFoto()));
}
// ...
return $this->render('CustomCMSBundle:Producten:index.html.twig', array(
'entities' => $entities,
'images' => $images,
));
View
{% for key, entity in entities %}
{# ... #}
<img alt="Embedded Image" src="data:image/png;base64,{{ images[key] }}" />
{# ... #}
{% endfor %}
in your entity write your image getter like this:
public function getFoto()
{
return imagecreatefromstring($this->foto);
}
and use it instead of the object "foto" property.
php doc for the function: http://php.net/manual/de/function.imagecreatefromstring.php
A more direct way, without extra work in the controller:
In the Entity Class
/**
* #ORM\Column(name="photo", type="blob", nullable=true)
*/
private $photo;
private $rawPhoto;
public function displayPhoto()
{
if(null === $this->rawPhoto) {
$this->rawPhoto = "data:image/png;base64," . base64_encode(stream_get_contents($this->getPhoto()));
}
return $this->rawPhoto;
}
In the view
<img src="{{ entity.displayPhoto }}">
EDIT
Thanks to #b.enoit.be answer to my question here, I could improve this code so the image can be displayed more than once.
As it is said before you must use base64 method, but for a better performance and usability, the correct option is creating a custom twig filter (Twig extension) as described here .
<?php
namespace Your\Namespace;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class TwigExtensions extends AbstractExtension
{
public function getFilters()
{
return [
new TwigFilter('base64', [$this, 'twig_base64_filter']),
];
}
function twig_base64_filter($source)
{ if($source!=null) {
return base64_encode(stream_get_contents($source));
}
return '';
}
}
In your template:
<img src="data:image/png;base64,{{ entity.photo | base64 }}">

Getting value from a twig to a controller

I'm using Symfony2 and my view is a twig/php. Can I get a value from a view? I tried like this:
listcurrency.twig.html:
{% for currency in liste %}
<TR>
<TH> <a href="{{ path('devises_disable', { 'id': currency.id }) }}"> {{currency.id}} </TH>
<TD> {{ currency.Name}} </TD>
<TD> {{currency.Enabled}}</TD>
</TR>
{% endfor %}
I call a route 'devises_disable' and pass a parameter currency.id.
EDIT:this is what i do with the value:
controller:
public function disableAction($id)
{
$em = $this->getDoctrine()->getManager();
$currency = $em->getRepository('DevisesBundle:Currency')->findOneById($id);
if (!$currency) {
throw $this->createNotFoundException(
'Aucun currency trouvée pour cet id : '.$id
);
}
$i=$currency->getEnabled();
if($i==0){$i=1;}else if($i==1){$i=0;}
$currency->setEnabled($i);
$em->flush();
$em->refresh($currency);
}
the route:
devises_disable:
path: /webmaster/listcurrency
defaults: {_controller: DevisesBundle:Default:disable}
the entity i'm trying to update doesn't change.No error message too!!
Try changing your route into :
devises_disable:
path: /webmaster/listcurrency/{id}
defaults: {_controller: DevisesBundle:Default:disable}
For checking the variable success or not passed into your controller put this in top of your controller :
echo 'This is the variable : '.$id;

Many-to-many relationship in doctrine2

I have a many-to-many relationship. Entities are Actor and Pys (Films and TV shows). When I want the cast of film the relation works fine (returns all the actor and actress of a film) but the problem comes when I want to retrive all the films of an actor.
This is the relation in the phpmyadmin:
This is the error that appear:
Key "actor" for array with keys "0" does not exist in ActorBundle:Default:actor.html.twig at line 3
Here the necessary code:
Actor's DefaultController
class DefaultController extends Controller
{
public function peliculasAction($actId)
{
$em = $this->getDoctrine()->getManager();
$peliculas = $em->getRepository('ActorBundle:Actor')->findPeliculas($actId);
return $this->render('ActorBundle:Default:actor.html.twig', array('peliculas' => $peliculas
));
}
}
ActorRepository
class ActorRepository extends EntityRepository
{
public function findPeliculas($actId)
{
$em = $this->getEntityManager();
$consulta = $em->createQuery('
SELECT p, d, a
FROM ActorBundle:Actor a
JOIN a.pys p JOIN p.director d
WHERE a.actId = :actId
ORDER BY p.pysAnyo DESC
');
$consulta->setParameter('actId', $actId);
return $consulta->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, 'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker')->getResult();
}
}
actor.html.twig
(...)
{% block title %}{{ peliculas.actor.actNombre ~ ' ' ~ peliculas.actor.actApellidos }}{% endblock %}
{% block article %}
<section class="article">
<div id="datos-actor-director-genero">
<strong>{{ 'Nombre:' | trans }} </strong>{{ peliculas.actor.actNombre ~ ' ' ~ peliculas.actor.actApellidos }}</br>
<strong>{{ 'Fecha de nacimiento:' | trans }} </strong>{{ peliculas.actor.actFechaNacimiento | localizeddate('long', 'none') }}
</div>
<table>
{% for pys in peliculas %}
<tr id="fechaVotacion">
<td colspan="4">{{ pys.pysAnyo }}</td>
(...)
You are getting a doctrine collection from
$em->getRepository('ActorBundle:Actor')->findPeliculas($actId);
So in Twig, peliculas is a collection too, do a for..in or peliculas[0] if you only want the first element.
peliculas has no key actor, only numbres (0 : first actor, 1 : second actor, etc...)

Resources