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;
}
...
}
Related
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 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%}
I use This function to get data
public function UserAction()
{
$easyuser = $this->getDoctrine()->getrepository('AppBundle:User')->findall();
foreach($easyuser as $user){
$id = $user->getid();
$username = $user->getUsername();
$email = $user->getEmail();
$roles = $user->getRoles();
}
return $this->render('easycall/user.html.twig', ['easyuser' => $easyuser, 'roles' => $roles]);
}
and in twig i use this code to show data
{% for entity in easyuser %}
<tr>
<td>{{entity.id}}</td>
<td>{{entity.username}}</td>
<td>{{entity.email}}</td>
{% for role in entity.roles %}
<td>{{role}}</td>
{% endfor %}
</tr>
{% endfor %}
The problem is that i get all the roles if the user is ROLE_SUPER_ADMIN, i want to get only the first value from every array.
i tried something like reset() but it did'nt work, any suggestion??
This is also a picture how the results look likes.
1st item from array shoul be somethink like
{{entity.roles | first}}
but it's simply 1st item from array, i'm not sure if it always be more "powerfull role"
I am creating a Task Management Application Demo project just to get the hold of symfony2. So far I have finished CRUD operations.
This is my defaultcontroller. I am calling the getNumberOfTasks() here.
namespace TaskManagerBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use TaskManagerBundle\Entity\Projects;
use TaskManagerBundle\Form\ProjectType;
class DefaultController extends Controller
{
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('TestBundle:Projects')
->findAll();
$tasks = $em->getRepository('TestBundle:Projects')
->getNumberOfTasks();
return $this->render('TestBundle:Default:index.html.twig', [
'projects' => $entities,
'tasks' => $tasks,
]
);
}
This is my ProjectRepository, where I have defined the getNumberOfTasks method.
<?php
namespace TaskManagerBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
use TaskManagerBundle\Entity\Projects;
use TaskManagerBundle\Entity\Tasks;
/**
* ProjectsRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class ProjectsRepository extends EntityRepository
{
public $id;
public function getNumberOfTasks()
{
//$projects = new Projects();
$query = $this->getEntityManager()
->createQuery(
'SELECT p FROM TestBundle:Tasks p WHERE p.projects = :project_id'
)
->setParameter('project_id', $this->id )
->getResult();
return count($query);
}
}
This is my index.html.twig
{% for project in projects %}
<tr>
<td>
{% if project.completed == 0 %}
{{ tasks }}
{% else %}
Completed
{% endif %}
</td>
</tr>
{% endfor %}
I am trying to get the number of tasks for each project. How do I do that?
In yii2 I could just do $this->id but here I am not able to do that.
You don't need the "getNumberOfTasks" method at all.
What you should be doing is specifying the associations in your Doctrine Entities.
You can read about it here:
http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html
Based on your naming, I would suspect a OneToMany relationship from Project to Task, and a ManyToOne relationship from Task to Project.
When mapped correctly, in Twig you can then use:
{{ project.tasks|length }}
to get the number of tasks in a project. All you need to do is to pass the Project Entity to Twig and Symfony will handle the rest, including loading in all relationships.
This is the best way of doing what you are trying to do.
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 }}.