I found a performance problem on my website.
I have an entity "Cart" with a oneToMany relation. When I call the getter method in a view that gives about 2000 queries. Then the performance of page decreases very strongly.
My entity Cart with OneTMany assoc :
class Cart {
/**
* #ORM\OneToMany(targetEntity="Comiti\UserBundle\Entity\Subscription", mappedBy="cart")
*/
protected $subscriptions;
}
My entity Subscription with ManyToOne assoc :
class Subscription {
/**
* #ORM\ManyToOne(targetEntity="Comiti\UserBundle\Entity\Cart",inversedBy="subscriptions")
* #ORM\JoinColumn(name="cart_id", referencedColumnName="id")
* #JMS\Exclude()
*/
protected $cart;
}
My twig view with call of getSubscriptions() producing a lot of database requests :
{% for subscription in cart.subscriptions %}
What can I do to get better performances on it?
The problem you're experiencing is called the N+1 problem. You're fetching an entity which has an association that you then traverse and query again. In your concrete example, this occurs in this loop, assuming that your subscriptions have a cost:
{% for subscription in cart.subscriptions %}
{{ subscription.cost }}
Given that you've queried for a cart, you haven't loaded all its subscriptions and its attributes, and that's happening at loop time. To get around this, you should do a fetch join on your cart with subscriptions:
// in CartRepository
public function findCartWithSubscriptions($cartId)
{
$qb = $this->createQueryBuilder('c');
$qb->leftJoin('c.subscriptions', 's')
->where("c = :cart")
->setParameter("cart", $cartId);
return $qb->getQuery()->getResult();
}
This will hydrate for you a Cart object with its subscriptions loaded in memory.
Related
I have a problem, it's for a school project and I need to allow duplications of the same relation between two entities on my app using Symfony 5 & Doctrine & postgresql .
I have a basicly a ManyToMany relation between Order and Products, I don't want to add fields for quantity, so I'm looking to count the number of occurences of the a same relation id_order & id_product on my order_product table, but I can't persist more than one same relation between order & product.
I searched and mainly saw people tryng to avoid duplications of the same relation, i'm looking for the exact contrary.
Thx
When using relation with Many on at least one side of the relation, you get Collection on the opposite side. On the collection you can call count() method.
So if you need to calculate quantity of Products in your Order, your Order entity can look like this:
/** #Entity */
class Order
{
...
/**
* #ManyToMany(targetEntity="Product", inversedBy="orders")
* #JoinTable(name="order_product")
*/
private $products;
public function __construct()
{
$this->products = new ArrayCollection();
}
public function countProducts(): int
{
return $this->products->count();
}
public function countProductsById(int $productId): int
{
return $this->products->filter(static function(Product $product) use ($productId) {
return $product->getId() === $productId;
})->count();
}
...
}
PS: Also be aware that word Order is a reserved word in PostgreSQL. You need to either name your Order entity differently or escape the naming correctly.
I have a relation in Doctrine2 #ORM\OneToMany, suposing that i have table school and student, in the entity school i have the #ORM\OneToMany column students,
and i also have a virtual deletion column deleted_at, so every student that has the deleted_at different of null is a deleted student that is supposed not to appear in the column #ORM\OneToMany $students. How can i make this filter?
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="App\Oceano\Entities\Student",
* mappedBy="cartCore",
* cascade={"all"}
* )
*/
private $students;
So, when i call for school students, it is retrieving also the deleted ones.
$schoolObj->getStudents();
Any Solution using annotation or some clean change?
You practically described Laravel's soft deleting feature. So, if you use it, you do not need to do anything and soft deleted students will not appear. You just need to add Illuminate\Database\Eloquent\SoftDeletes trait to the Student model.
If you're using some own functionality, create a local scope in the Student model:
public function scopeNotDeleted($query)
{
return $query->whereNull('deleted_at');
}
And use it:
Student::notDeleted()->get();
Or:
$school->students()->notDeleted()->get();
You can use Criteria filter class in your entity to students collection which are not deleted
protected getStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->eq('deleted_at', null));
return $this->students->matching($criteria);
}
To get deleted students you could write it like
protected getDeletedStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->neq('deleted_at', null));
return $this->students->matching($criteria);
}
How filter data inside entity object in Symfony 2 and Doctrine
I'm a big begginer in Symfony, coming from Ruby on Rails world and trying to get same behaviours.
I have a simple application with 2 entities : Product and Category, with the relation Product belongs to Category and Category has many Products.
class Category {
[ ... ]
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category", cascade={"all"})
*/
protected $products;
}
class Product {
[ ... ]
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products", cascade={"all"})
*/
protected $category;
}
What I'm trying to do is to delete every nested products when I'm deleting a Category.
My current action looks like
public function deleteAction($id, Request $request)
{
$repository = $this->getDoctrine()->getRepository('AppBundle:Category');
$category = $repository->find($id);
$em = $this->getDoctrine()->getManager();
$em->remove($category);
$em->flush();
return $this->redirect('/categories/');
}
A simple method could be remove all the products in the controller, but it's not very maintainable and not very object oriented. I'm looking about a practice to remove all the products of the deleted category directly in the model. A method, in RoR, is the callbacks (named after_destroy), automatically called when the object is destroyed. Is there any looking-like method in Symfony ?
I have an entity called Menu which contains collection of Category entity, which contains collection of Product entity.
$menu->fillMenu($categoriesData);
$categoriesData contains all categories and products in an array. fillMenu function is updating categories and products, adding new categories and products, and deleting categories and products which are not in $categoriesData. But this function is only working on collections in memory, not on database. So I added code:
foreach ($menu->getCategories() as $category) {
foreach ($category->getProducts() as $product) {
$em->persist($product);
}
$em->persist($category);
}
$em->flush();
Now all updated and new categories and products are saved in database but how to remove categories and products which were deleted by fillMenu function? Is there any possibility get reference to deleted objects from collections and then code would be:
$em->remove($deletedCategory);
EDIT
I think, that I should return all entities to be deleted by fillMenu function. And then iterate through them and do $em->remove($entity). But I think it's not very nice way. What do you think, what is the proper way to do this operation in Symfony2?
Try $em->flush() after loop
foreach ($menu->getCategories() as $category) {
foreach ($category->getProducts() as $product) {
$em->persist($product);
}
$em->persist($category);
}
$em->flush();
First, while you are updating a record or removing you don't need to persist it just you can call $em->remove($object); then $em->flush();
Second, you can do it by coding but I would recommend to let deleting be done by Doctrine; in this case you just need to change the mapping and use onDelete="CASCADE"
Since I don't know your mapping I can provide some sample:
/**
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $category;
Now when you run the following code your child products will be removed, too
foreach ($menu->getCategories() as $category) {
$em->remove($category);
}
$em->flush();
The context
I'm working on an online shop. Every Product on the website can have several ProductVariations (e. g. size, color, ...) which values are a list of ProductVariationValue (e. g. XL, L, Blue, Red, ...).
Each ProductVariationValue is bound to a ProductVariation (e. g. you can't choose Red for Size).
I'm trying to create a stock management system which tracks the amount of in-stock (Product, ProductVariations, ProductVariationValues) combination.
Product <--> ProductVariation <--> ProductVariationValue
I've managed to do it by creating a Stock entity holding a Product and a collection of ProductVariationValues:
/** #ORM\Entity */
public class Stock
{
// ...
/** #ORM\ManyToOne(targetEntity="AcmeDemoBundle:Product") */
protected $product;
/** #ORM\ManyToMany(targetEntity="AcmeDemoBundle:ProductVariationValues") */
protected $productVariationValues;
/** #ORM\Column(type="integer") */
protected $number = 0;
// ...
}
When the user adds a Product to his cart, I create an instance of OrderItem which holds the ProductVariationValues they selected:
/** #ORM\Entity */
public class OrderItem
{
// ...
/** #ORM\ManyToOne(targetEntity="AcmeDemoBundle:Product") */
protected $product;
/** #ORM\ManyToMany(targetEntity="AcmeDemoBundle:ProductVariationValues") */
protected $productVariationValues;
// ...
}
The problem
I understand the Stock entity is similar to the OrderItem entity. However, when the user adds a Product to his cart, and I'm building the OrderItem entity, I cannot find how to retrieve the Stock instance to check whether the Product with its ProductVariationValues are in stock, because I can't find which relation to add in the Stock entity.
Indeed, the Stock entity cannot be identified by the $product or $productVariationValues relations by themselves: they need to be put together.
What I've tried to do
In the controller, given the $product and an array of $productVariationValues, the user selected, I've tried to set up a query to retrieve the Stock instance.
class StockRepository
{
public function retrieveStock($product, $productVariationValues)
{
$this->getQueryBuilder('s')
->leftJoin('s.product', 'p')
->leftJoin('s.productVariationValues', 'pvv')
->where('s.product = ?')
->andWhere('s.productVariationValues = ?')
->setParameter(1, $product)
->setParameter(2, $productVariationValues);
// ...
}
}
This code does not work as the setParameter() method can not accept array or ArrayCollection parameters.
Either way, this solution would not be optimal as this code must be run from the controller and I wouldn't be able to run it from the OrderItem entity.
Is my entity model wrong? Is there a simple way to add a Stock relation in my OrderItem class? Or at least to retrieve a Stock item, given its Product and ProductVariationValues?
You need to use an IN clause, and iterate over all the values; something like:
class StockRepository
{
public function retrieveStock($product, $productVariationValues)
{
$qb=$this->getQueryBuilder('s');
$qb->leftJoin('s.product', 'p')
->leftJoin('s.productVariationValues', 'pvv')
->where('s.product = ?')
->setParameter(1, $product)
;
$stockVariationValues=$qb->getQuery()-
>getSingleResult()->getProductVariationValues();
$qb=$this->getQueryBuilder('s');
$qb->leftJoin('s.product', 'p')
->leftJoin('s.productVariationValues', 'pvv')
->where('s.product = ?')
->setParameter(1, $product)
;
$stockVariationValues->map(function ($element) use ($qb){
$qb->andWhere($qb->expr()
->in('s.productVariationValues'
, "'".implode(","
,$productVariationValues->toArray())
."'")
);
});
// ...
}
}