Consider the following case: I have two entities: Article and ArticleComment:
// \AppBundle\Entity\Article
/**
* #ORM\OneToMany(targetEntity="ArticleComment", mappedBy="article")
*/
private $comments;
I need to store the amount of comments in a field on the article (eg. articles.comments_count). The field needs to be updated whenever a comment is created or deleted.
Previously I used the CakePHP framework which has built-in CounterCache behavior which does this automatically. I've tried my best to find something similar for Doctrine 2 (starting with DoctrineExtensions library) but nothing seems to do what I'm looking for.
Any library that does this? Or do I have to come up with my own solution?
Edit: I've tried using Entity Events but I require this behavior on many entities so I'm interested in a reusable solution
You can take a look at the extra lazy associations. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html
This way you don't need to store the comment_counter as you will be able to use the count() function on your collection without loading the full collection.
Internally, Doctrine will issue a "select count" query.
Here is another answer which avoids storing this kind of aggregate and enables you to use the paginator as you've requested in comments. I didn't test it yet so there could be some errors.
$qb = $em->createQueryBuilder();
$qb
->select('a.title, a.author, count(c)')
->from('Article', 'a')
->leftJoin('a.comments', 'c')
->groupBy('a.id');
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate($qb, $page, $limit);
As I said, this issue is not really Doctrine related because your initial model design is bad.
Usually, you don't need to store an aggregate which can be computed with a count/groupby query.
This kind of aggregate is useful when you have a lot of joined entities which creates a real overhead during computing. Else, you don't need it.
Related
Hi I want to delete the product in the other table when I delete a product. And I want to do this on the controller.
$deleteQuery = $this->getDoctrine()
->getManager()
->createQueryBuilder('d')
->delete('TechnoSiteBundle:Sepet', 's')
->where('s.id = ' . $request->get('id'))->getQuery();
$deleted = $deleteQuery->getResult();
OR
$deleteQuery = $em->getRepository('TechnoSiteBundle:Ebatlar')->createQueryBuilder('table1')
->where('table1.id')
->leftJoin('table1.sepet', 'table2')
->andWhere('table2.ebat1')
->delete('table1.id = table2.productid')
->getQuery();
$deleted = $deleteQuery->getResult();
I want to delete two related products. I get this error when I use Cascade;
2/2DBALException: An exception occurred while executing 'DELETE FROM ebatlar WHERE id = ?' with params [30]:
It's not very clear what you should do with so little information about your entities.
Some things to explore:
If your two product rows are related, you might want to look into using a cascade={"remove"} annotation somewhere.
Nothing prevents you from running two queries if you want to manually delete two entities.
Some notes about best practice with Doctrine, Symfony and MVC in general (not directly related to your question, and you don't have to follow these guidelines, but it's things you definitely should follow as that will make your life much easier in the long run):
You're not supposed to put too much logic in controllers. In Symfony, you want your controllers to be almost empty. You create a service with the logic in it, and call it from your controller in the appropriate action. This has many advantages that you'll notice as your project grows: less code duplication, the freedom to change your model without changing your controllers, etc.
It's better to bind parameters than to use concatenation like ->where('s.id = ' . $request->get('id')). This $request->get('id') is something the user can forge to insert basically anything he wants in your where clause.
I'm dealing with a fairly complex querybuilder (in a repository function), which is using a lot of things like partial c.{id,name,created} and a bunch of fetchjoins to try and keep both the amount of data and the amount of queries down.
This is however not enough, but the front end representation is paged anyway, so I'd like to make some ajax calls to fetch data when needed.
$qb = $this->createQueryBuilder('c')
->select('a whole bunch')
->join('many joins')
->setFirstResult(0)
->setMaxResults(10)
The above method doesn't work for complex joins, I do setMaxResults(10) and get 2 results, and setMaxResults(1000) gets me 118 results. Doctrine advises to use their Pagination class, as it will handle the count/iteration properly.
Now that works all fine if I loop over the Iterator object provided by new Paginator($query, true), but the code calling the repository function expects to get an Array from getArrayResult.
The iterator contains full entity objects, which means I would have to rewrite all the services to use methods instead of array keys.
Is there a way to use the Paginator with an ArrayResult?
Add Hydration Mode to your query.
//Set query hydration mode
$query->setHydrationMode(\Doctrine\ORM\Query::HYDRATE_ARRAY);
I have a few entities that have a few associations. They are loading up fine now. So I basically have a customer entity, that relates to an address entity. I also have a receipt transaction that relates to a customer.
When I retrieve a small set of customers I want to retrieve their related receipts but that set is huge. I want just the receipts from the last 2 weeks.
I thought that I could use a custom repository then use a function like customer->getRecentReceipts() but that doesn't work since the customer entity doesn't know about the repository. And from reading on this forum, people seem to say not to use the repository this way. How should I structure things to limit the loading of my receipt entities. I'm trying to avoid loading all then sorting them with a php routine.
i think you forgot to link your repository at the entity.
Exemple:
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\ProductRepository")
*/
class Product
{
//...
}
that's when you can use your repository functions
There's nothing wrong with making a repository method to do this. You don't have to deal directly with the customer entity to get its receipts.
I'd just create a simple method in ReceiptRepository like (Syntax might be not quite right, I don't have an IDE handy right now)
getReceiptsForCustomerByDate(Customer $customer, \DateTime $createdAfter=null) {
if (!$createdAfter instanceof \DateTime) {
$createdAfter = new \DateTime('now -2 weeks');
}
$qb = $this->getEntityManager()->createQueryBuilder();
return $qb->select('c, r')
->from('YourBundle:Customer', 'c')
->join('c.receipt', 'r')
->where($qb->expr()->eq('c', ':customer')
->andWhere($qb->expr()->gt('r.createdAt', ':createdAfter')
->setParameter('createdAfter', $createdAfter)
->setParameter('customer', $customer)
->getQuery()->getResult();
}
The above means your fetched customer entities will only have the relevant receipts. Because we haven't lazy loaded the receipts, $customer->getReceipts() will only return the receipts we have specified by date.
No, you can't call this from your Customer entity, but there's no reason you can't call it in a controller method. This is a perfectly valid way to get things done in Symfony.
You could easily modify this to get many customer receipts by passing an array of customers.
I'm building an API on Symfony2 an I have a Model ItemOrder with a ManyToOne relationship to a Model Item. I have a few Items in my database, and I want to add an ItemOrder that points to an Item that is already in the database, and whose id I know. So my first approach is this:
$item = new Item();
$item->setId(2);
$orderItem = new OrderItem();
$orderItem->setItem($item);
$em->persist($orderItem);
$em->flush();
However, Symfony2 understand that I'm trying to create a new item. I know that a valid approach would be fetching the Item object with the entity manager, and then assign it to the ItemOrder, but I think it's a not very efficient way of doing it.
So how should this be done?
What you're looking for is called Partial Reference.
$item = $em->getPartialReference('Item', 2);
$orderItem = new OrderItem();
$orderItem->setItem($item);
$em->persist($orderItem);
$em->flush();
However, please read the What is the problem? paragraph carefully, it might be safer to query for full entities by ids instead.
getPartialReference() vs getReference()
I've originally also found what forgottenbas linked, but I don't think it's the correct solution.
These two methods seem to be almost identical and both are referenced in official documentation.
Seems that only reasonable way to determine which is best is by looking directly into source code: getReference() and getPartialReference().
Straight up you will notice that getPartialReference() is better documented with a clear description of a use case that exactly matches yours:
* The use-cases for partial references involve maintaining bidirectional associations
* without loading one side of the association or to update an entity without loading it.
If you investigate the code for getReferece() you will notice that in some cases it will result in a database call:
if ($class->subClasses) {
return $this->find($entityName, $sortedId);
}
and finally, getPartialReference() marks partial reference as read-only, better defining it's purpose:
$this->unitOfWork->markReadOnly($entity);
You can create special reference object. More info see on this question
$item = $em->getReference('FQCNBundle:Item', 2);
$orderItem = new OrderItem();
$orderItem->setItem($item);
$em->persist($orderItem);
$em->flush();
I have two entities that represent users (User) and friendship requests (FriendshipRequest). There is a oneToMany relationship between User and FriendshipRequest, so Doctrine creates a method that is called getFriendshipRequests() in the class User. This is ok, but FriendshipRequest has an attribute that is called status, so I would like that the User class could filter the friendship requests associated to it attending to their status. I have read Doctrine documentation, and I found out this:
Natively you can’t filter associations in 2.0 and 2.1. You should use
DQL queries to query for the filtered set of entities.
According to this, I suppose that I should create a FriendshipRequest repository and create a method called "findByStatusAndUser" (or something like that), but I think that's a crappy solution.
I would like to have a method in the User entity, like getPendingStatusRequests(). Is this possible? If it isn't, what would be the best solution?
As of Doctrine 2.3 you can use matching and Criteria.
Then you could use getPendingStatusRequests() in User entity just like you wanted.
For your example the code would look like this:
public function getPendingStatusRequests()
{
$criteria = Criteria::create(); //don't forget to use Doctrine\Common\Collections\Criteria;
$criteria->where(Criteria::expr()->eq('status', 1));
return $this->friendshipRequests->matching($criteria);
}
I think that "getPendingRequestsForUser($user)" method in the FriendshipRequest repository should be a good solution. Inside this method you just need to create an appropriate DQL.
This is a good solution, because all of the logic should be moved to repositories, leaving entities as small and clean as possible.
UPD: Also, you could use findBy method, as described here, ex:
$pendingRequests = $em->getRepository('MyBundle:FriendshipRequest')->findBy(
array('user' => $user->getId(), 'status' => 1)
);
But for me, first method is preferred.
You can certainly add getPendingStatusRequests() to user and then have it cycle through all the friendship requests and only return those with the appropriate status.
The only potential problem is that all of the friendship requests will always be loaded including those you don't need. It is up to you to decide if this is a real problem or not. It might be that once a friendship request is processed then it is removed so a user won't have many requests at any given time.
If you do want to avoid loading all the requests then make a query and use the WITH expression on your join clause. Something like:
$qb->leftJoin('user.friendshipRequests','request',
Expr\Join::WITH, $qb->expr()->eq('request.status', $qb->expr()->literal('Pending')));
And since you are using S2 I would not fool around with repositories. Just make a service called UserManager, inject the entity manager, and give it a method called loadUserWithPendingFriendshipRequests.