symfony createQueryBuilder leftjoin with certain selected fields - symfony

I made a query for selecting certain fields together with a leftjoin.
However I cannot get it to work both at the same time (have a leftjoin and certain selected fields).
$query = $em->getRepository(Product::class)
->createQueryBuilder('p')
->select('p, p.slug, p.price, p.title_x AS title, pc')
->leftJoin('p.productimgs', 'pc')
->getQuery();
I call the array with
{% for item in article.productimgs %}
but i get the error: Key "productimgs" for array with keys "0, slug, price, title" does not exist
I also tried to call it with the function
{% for item in article.getProductimgs() %}
but then i get this error : Impossible to invoke a method ("getProductimgs") on an array.
I am not so good with doctrine / query building.
The productimages is a onetomany relation in the product entity.
it's a symfony 5 project
All help appreciated, thank you!

Since you are mixing the entities and specific columns in the select the hydrated results will actually be arrays and not Products. You can see it's structure with
{{ dump(article) }}
If you just want to eager load the related product images in the one query use
$query = $em->getRepository(Product::class)
->createQueryBuilder('p')
->select('p, pc')
->leftJoin('p.productimgs', 'pc')
->getQuery();
This will hydrate the results as Products so you can access its properties by the Twig shorthands or as functions:
{{ article.slug }}
{{ article.getSlug() }}
If you access the Product's images it will not execute a new database query to fetch them since you added them to the select part and they were already hydrated into the result objects:
{{ article.productimgs }}
{{ article.getProductimgs() }}

Related

How can I select count(field) and still get an object with createQueryBuilder

I want to access a query result in twig as a whole object. In the query however, I want to select just several fields from the tables + count. In my example I have a company, which is owned by an owner and has several employees. I want to create a query, which gives me a list of companies ordered by count of their employees.
$qb = $this->createQueryBuilder('c');
$qb->select('count(e.id), partial c.{id, name}, partial o.{id, name}');
$qb->leftJoin('c.employees', 'e');
$qb->innerJoin('c.owner', 'o');
$qb->groupBy('c.id');
The query works fine, however I get an error, when I want to access other objects liked with normaly in the company entity (there is an collection of image entities, and each image entity contains the path to the image). The query does not load the whole company entity with its dependencies... I cannot acces the "firstImage" method, which returns the first image from the collection. I get this error
Key "firstImage" for array with keys "0, 1" does not exist in
MyBundle:List:results.html.twig at line 6
Note that leaving out the "employees count" makes it whole work.
$qb = $this->createQueryBuilder('c');
$qb->select('partial c.{id, name}, partial o.{id, name}');
$qb->leftJoin('c.employees', 'e');
$qb->innerJoin('c.owner', 'o');
$qb->groupBy('c.id');
Your object will be the first element of result array and the count() value will be the second. You can access to object properties and methods in twig by the following way:
{{ object[0].firstImage }}
{{ object[0].name }}
And the count of employees can be accessed as:
{{ object[1] }}
Also you may define the name for your count() value in the query
$qb->select('count(e.id) AS empQty, partial c.{id, name}, partial o.{id, name}');
and then you can access this value in twig as:
{{ object['empQty'] }}

Best way to filter a relationship from a twig template

in my Symfony2 project, I've two entities: Spot and Weather, with a one-to-many relationship "weatherReports" between the two entities.
Some weather reports are outdated, so I would like do create an "activeWeatherRecords" method to filter the Weather entities in a Spot entity.
Unfortunately, I can't see how to do this. The idea is not to fetch objects from the controller since the Spot objects are favorited, linked to an User object and accessed directly from a twig template.
So here is the question: What's the best way to filter a relationship directly from a twig template?
UPDATE 09/11/2013
I managed to filter my relationship with a filtering method on my relationship.
In my spot entity, I declared a getActiveWeatherRecords() method:
public function getActiveWeatherReports()
{
// Date
$date = new \DateTime(date('Y-m-d 12:00:00', time()));
// Criteria
$criteria = Criteria::create()
->where(Criteria::expr()->gte("date", $date))
->orderBy(array("date" => Criteria::ASC))
;
return $this->weatherReports->matching($criteria);
}
And I can call this method from a twig template as simply as follow:
[...]
{% for weatherReport in spot.activeWeatherReports %}
[...]
{% endfor %}
[...]
One way is to create a finder method that fetches only active records. You could put that method in a Doctrine repository, call it from your controller (or the Service Layer) and pass it to your template.
Another way is to add a filtering method right to your entity. This way you don't have to call a separate method and pass the result to a template — the entity you pass to the entity will be enough.

How to use custom repository methods in Twig template?

Assuming I have an entity User and an entity Book and they're both joined by User.bookId = Book.id (this marks a user owns a certain book, relation type oneUserToManyBook).
If I now want to execute a performance friendly fetch with Doctrine's DQL or QueryBuilder for all Books a User has read, what is the best way to implement this in a Symfony2/Doctrine2 webapp, so that I can use them in my User loop in a Twig template?
Twig
{% for user in users %}
{{ user.name|e }}
{% for address in user.getAddressesByUserId(user.getId()) %}
{{ address.city }}
{% endfor %}
{% endfor %}
I see two approaches, but both don't lead to my target:
1st approach
Create a custom repository class BookRepository:
public function getBooksOwnedByUser($user_id) {
return $em->createQuery('SELECT b.title
FROM MyBundle\Entity\User u,
MyBundle\Entity\Book b
WHERE u.book_id = b.id'
AND u.id = :user_id)
->setParameter('user_id', $user_id)
->getResult();
}
Problem: Works fine, but I cant call getBooksOwnedByUser() in my Twig template (because it's not tied to the entity User, but to it's repository, which is a subclass of Doctrine\ORM\EntityRepository.
2nd approach
Execute the same query as above - not in my UserRepository, but directly in my User entity class.
Problem here: I could call this method in my Twig template, but I cannot (and should not) use the EntityManager in my User entity class.
It's best if you make a relationship from User to Books. Assuming you have made this relationship you can make your query like this:
public function getBooksOwnedByUser($user_id) {
return $em->createQuery('SELECT u, b
FROM MyBundle\Entity\User u
JOIN u.books b
WHERE u.id = :user_id')
->setParameter('user_id', $user_id)
->getResult();
}
Then in your controller:
$em = $this->getDoctrine()->getManager();
$user_with_books = $em->getRepository('MyBundle\Entity\User')
->getBooksOwnedByUser($user->getId());
return $this->render('YourTemplate.html.twig', array(
'user_with_books' => $user_with_books,
));
In twig:
{% for book in user.books %}
{{ book.title }}
{% endfor %}
Some considerations:
For multiple users you will have to change the query (lazy loading is possible but not advised).
If it's a lot of data you can get a performance boost by getting a scalar result (Array)
If you need different queries for the user that can not be combined you will have to store different variables (objects or arrays). That's why I named it "user_with_books". But if you only have this user in your template you can just as well call it "user".
user.getAddressesByUserId(user.getId()) <-- passing data from one model to query is the responsiblity of the controller (or a service). Best practice is to avoid doing this in your template.
So the answer:
You can not do anything with a custom repository method because it's a function. A function on itself doesn't represent any data. So this is a way you can retrieve the actual data with that function and display that.

Check joined entity from Twig in Symfony2

I want to display a table with some entity relations from a Doctrine DQL query.
My "main" entity Lead has a relation with Tour like this:
class Lead {
/**
* #var integer $tourId
* #ORM\Column(name="`tour_id`", type="integer")
*/
private $tourId;
/**
* #var Tour $tour
* #ORM\ManyToOne(targetEntity="Tour")
* #ORM\JoinColumn(name="tour_id", referencedColumnName="id")
*/
private $tour;
...
}
And I get the data from DB with a Doctrine2 DQL:
SELECT l, c
FROM BuvMarketplaceBundle:Lead l '
JOIN l.client c
Note that I don't JOIN with Tour becouse not all Leads have a Tour associated, this field can be null.
Then I am printing like this:
{% for lead in leads %}
{{ lead.id }}
{% if lead.tour %}
{{ lead.tour.name }}
{% endif %}
{% endfor %}
The problem comes where lead.tour has a numeric value, but this value does not exists in the tours table (because it has been deleted). I get the "Entity was not found." exception that refers to the lead.tour that does not exist in the DB.
I tried to check lead.tour with is defined, is not null but nothing works.
Twig is not supporting the cheking of types, so there is no basic function available to check lead.tour is object or similar.
Is there any way that can check an object from Twig or left join from the DQL?
A left join will solve the problem of not all leads having a tour.
SELECT lead,tour
FROM BuvMarketplaceBundle:Lead lead
LEFT JOIN lead.tour tour
And as my comment indicates, in a properly setup Doctrine 2 model, it will not be possible for lead to point to a tour record that does not exist.

Querying a collection within an entity

I currently have a simple one to many relationship between products and multiple deals (a table of 1 million deals in total) associated with the products.
What I'm trying to do is loop through the top 10 products and select the top deals relating to the product.
What would be the best way to achieve this in Doctrine 2? I was contemplating adding a method such as getTopDeals within the product entity, and then calling it within twig as I looped through each product like so:
{% for product in popular_products %}
{% set deal = product.getTopDeal() %}
{{ product.title }} - {{ deal.title }}, {{deal.price }}
{% endfor %}
However, I've read that generally it is frowned upon adding methods such as this into models, so I'm at an end as to what the best way to do this is.
Make a method in your Deals repository to accept a parameter and return the topdeal. In your controller, array_map() your products to produce an array of deals keyed by product. Then pass the deals array along with your products array to your template.
edit: sample requested:
Repository:
public function getTopDealProduct($productid)
{
$em=$this->getEntityManager();
$qb = $em->getRepository('Bundle:Deal')->createQueryBuilder('d');
$qb->join('d.product', 'p');
$qb->setMaxResults(1);
$qb->addOrderBy('d.price');
$query = $qb->getQuery();
$results = $query->getResult();
return $results;
}
Controller:
public function s2JUsorAction(Request $request, $id)
{
$dealrep = $this->em->getRepository('Bundle:Deal');
$prodrep = $this->em->getRepository('Bundle:Product');
$products= $prodrep->getProducts(); // Not shown here, write this
$deals= array_map(function($element) use ($dealrep){
return $dealrep->getTopDealProduct($element->getId());
}
,$products);
return $this->render('Bundle:Product:Deal.html.twig', array(
'products' => $products
,'deals' => $deals
));
}
The best practice is, "fat models, thin controllers". The logic for selecting the top deals for a product definitely has a place on the model, if the model itself is capable of doing this filtering, eg. it only needs the deal objects, which it has a relation with. For this, you could use the Criteria API, something like:
use Doctrine\Common\Collections\Criteria;
class Product {
private $deals; // many-to-many to Products
public function getTopDeals() {
$criteria = Criteria::create()->orderBy(array('price', 'DESC'))->setMaxResults(10);
return $this->deals->matching($criteria);
}
}
If the selection logic is more complicated, and needs to reach into the entity manager, then it is better suited for placing on an EntityRepository.

Resources