How to update collections of entities in symfony2? - php

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();

Related

Allow ManyToMany Duplications on Symfony 5 (Doctrine)

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 am trying to mix pivot table values in Laravel

I have three table category and products with its pivot table as category_product
One category has many products
I want the category name in attributes of products with its own field.
$saijal = "Our Products";
$products = Product::orderBy('created_at','DESC')->with('categories')->get();
$abc = new Collection();
foreach($products as $pro)
{
$abc->put($pro->id,$pro);
}
foreach($products as $k => $pro)
{
$abc->get($pro->id)->children = new Collection();
$abc->get($pro->id)->categoryName= $pro->categories->name;
$abc->get($pro->id)->children->push($pro);
unset($pro[$k]);
}
dd($abc);
For example:
If I'm understanding correctly, you want to be able to access an attribute called "categoryName" directly from the product object. In order to do this, simply set up an attribute getter in the Product.php model that looks like this:
public function getCategoryNameAttribute()
{
return $this->category->name;
}
Then you can simply reference the category name like this:
$product->categoryName
The main issue I see is that you are referring to "categories" in your code as if it is plural. If the product belongs to many categories, the solution would be different and your relationships would require a pivot table, as you describe. But if the product just belongs to one category, as your code implies, the solution above should be sufficient and you actually do not need a pivot table. You would just have a category_id column directly in the products table.
If you do want to have multiple categories for each product, you could do something similar but return an array:
public function getCategoryNamesAttribute()
{
return $this->categories->pluck('name');
}
This would return an array of category names associated with this Product, and would be accessible as:
$product->categoryNames
You can add custom attributes to appends array (not attributes array).
class Product extends Model
{
protected $appends = ['category_names'];
public function getCategoryNamesAttribute()
{
return $this->categories->map->name;
}
}
you can access this attribute as,
$product->category_names;
Note
The benefit of adding this to the appends array is. If you don't add this to the appends array, When you call toArray() or toJson() or send this $product via json. you loses this attribute.

Symfony 2 OneToMany performance optimisation

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.

How to automatically remove nested objects in Symfony?

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 ?

Is it possible to pass an id instead of the entity when creating an association between two entities?

I have an Item entity and a Category entity. An Item has one Category. My mapping code looks like this:
// Item.php
/**
* #ORM\ManyToOne(targetEntity = "Category")
* #ORM\JoinColumn(name = "category_id", referencedColumnName = "id")
*/
protected $category;
To create the association, I use this method:
// Item.php
public function setCategory(Category $category) {
$this->category = $category;
}
This works fine as long as I first fetch the Category entity from the DB. But I'm wondering if it's possible to pass an id instead of the Category entity. I'd like to manually set the JoinColumn category_id with a scalar value. But sine category_id isn't an actual member of Item, I'm not sure how I can do this.
Use getReference:
$item->setCategory($em->getReference('Category', $id));

Categories