Spoiler: I think I found the answer but I'm not 100% sure ;)
I've been looking at this question for a while but I cannot manage to make it work. So I've create dummies Entities to test the relation and here they are:
- A
Product
can be in manyCart
- A
Cart
can contains severalProduct
- The
Product
in aCart
are order by aposition
Product
<?php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @ORM\Table(name="demo_product")
*/
class Product
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\OneToMany(targetEntity="CartHasProduct", mappedBy="product", cascade={"all"})
*/
protected $productCarts;
/**
* Constructor
*/
public function __construct()
{
$this->productCarts = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add productCarts
*
* @param \Acme\DemoBundle\Entity\CartHasProduct $productCarts
* @return Product
*/
public function addProductCart(\Acme\DemoBundle\Entity\CartHasProduct $productCarts)
{
$this->productCarts[] = $productCarts;
return $this;
}
/**
* Remove productCarts
*
* @param \Acme\DemoBundle\Entity\CartHasProduct $productCarts
*/
public function removeProductCart(\Acme\DemoBundle\Entity\CartHasProduct $productCarts)
{
$this->productCarts->removeElement($productCarts);
}
/**
* Get productCarts
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getProductCarts()
{
return $this->productCarts;
}
}
Cart
<?php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @ORM\Table(name="demo_cart")
*/
class Cart
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\OneToMany(targetEntity="CartHasProduct", mappedBy="cart", cascade={"all"})
*/
protected $cartProducts;
/**
* Constructor
*/
public function __construct()
{
$this->cartProducts = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add cartProducts
*
* @param \Acme\DemoBundle\Entity\CartHasProduct $cartProducts
* @return Cart
*/
public function addCartProduct(\Acme\DemoBundle\Entity\CartHasProduct $cartProducts)
{
$this->cartProducts[] = $cartProducts;
return $this;
}
/**
* Remove cartProducts
*
* @param \Acme\DemoBundle\Entity\CartHasProduct $cartProducts
*/
public function removeCartProduct(\Acme\DemoBundle\Entity\CartHasProduct $cartProducts)
{
$this->cartProducts->removeElement($cartProducts);
}
/**
* Get cartProducts
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getCartProducts()
{
return $this->cartProducts;
}
}
and finally CartHasProduct reference table
<?php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @ORM\Table(name="demo_cartHasProduct")
*/
class CartHasProduct
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity="Cart", inversedBy="productCarts")
*/
protected $cart;
/**
* @ORM\ManyToOne(targetEntity="Product", inversedBy="cartProducts")
*/
protected $product;
/**
* @ORM\Column(type="integer")
*/
protected $position;
public function __construct(Cart $cart, Product $product, $position=0) {
$this->cart = $cart;
$this->product = $product;
$this->setPosition($position);
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set position
*
* @param integer $position
* @return CartHasProduct
*/
public function setPosition($position)
{
$this->position = $position;
return $this;
}
/**
* Get position
*
* @return integer
*/
public function getPosition()
{
return $this->position;
}
/**
* Set cart
*
* @param \Acme\DemoBundle\Entity\Cart $cart
* @return CartHasProduct
*/
public function setCart(\Acme\DemoBundle\Entity\Cart $cart = null)
{
$this->cart = $cart;
return $this;
}
/**
* Get cart
*
* @return \Acme\DemoBundle\Entity\Cart
*/
public function getCart()
{
return $this->cart;
}
/**
* Set product
*
* @param \Acme\DemoBundle\Entity\Product $product
* @return CartHasProduct
*/
public function setProduct(\Acme\DemoBundle\Entity\Product $product = null)
{
$this->product = $product;
return $this;
}
/**
* Get product
*
* @return \Acme\DemoBundle\Entity\Product
*/
public function getProduct()
{
return $this->product;
}
}
I've created the Entities manually, adding the @ORM annotations to setup the relationship and then I've used app/console generate:doctrine:entities AcmeDemoBundle
to populate the getter
, setter
and __construct
Now I a controller I have to following code:
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class WelcomeController extends Controller
{
public function indexAction()
{
// Create a Cart Entity
$cart = new \Acme\DemoBundle\Entity\Cart();
// Create a Product Entity
$product = new \Acme\DemoBundle\Entity\Product();
// Add the Product into the Cart
$cart->getCartProducts()->add($product);
// Save the Cart
$em = $this->getDoctrine()->getManager();
$em->persist($cart);
$em->flush();
return $this->render('AcmeDemoBundle:Welcome:index.html.twig');
}
}
Doing so I have the following error coming up:
Found entity of type Acme\DemoBundle\Entity\Product on association Acme\DemoBundle\Entity\Cart#cartProducts, but expecting Acme\DemoBundle\Entity\CartHasProduct
So my question is how to add a Product into a Cart? Do I need to create the relation Object manually (CartHasProduct
)? I would think Doctrine would have done it. I looked everywhere on Doctrine documentation and I could not find an exemple of relationship with extra field.
I've also looked into the tests in the vendor, there is plenty of model (very interesting) but nothing with extra field in relationship.
I was thinking the create my own method in Cart like this:
public function addProduct(Product $product, $position=0) {
$relation = new CartHasProduct($this, $product, $position);
if (!$this->cartProducts->contains($relation)) {
$this->cartProducts->add($relation);
}
}
But I'd like to know if I need to implement it or if it's meant to be handled automatically?
#### UPDATE 1 ####
So I ended up adding this method addProduct
. The problem is that contains()
is not working as expected. So I tried to delete all Product
from the Cart
and add a new one.
Here is my function to delete the products:
/**
* Reset the product for the cart
*
* @return bool
*/
public function resetCart() {
foreach ($this->getCartProducts() as $relation) {
$relation->getProduct()->removeProductCart($relation);
$this->removeCartProducts($relation);
}
}
and here is how I call it:
$em = $this->getDoctrine()->getManager();
$cart->resetCart();
$em->persist($cart);
$em->flush();
But the records are not deleted in CartHasProduct table.
UPDATE 2I found what was the problem, you need to add orphanRemoval=true
in the OneTwoMany relation (on both side) if you want to delete the relationship between the 2 main Entity (Cart
and Product
):
/**
* @ORM\Entity
* @ORM\Table(name="demo_product")
*/
class Product
{
...
/**
* @ORM\OneToMany(targetEntity="CartHasProduct", mappedBy="product", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $productCarts;
And
/**
* @ORM\Entity
* @ORM\Table(name="demo_cart")
*/
class Cart
{
...
/**
* @ORM\OneToMany(targetEntity="CartHasProduct", mappedBy="cart", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $cartProducts;
...
/**
* Reset the product for the cart
*/
public function resetCart() {
$this->getCartProducts()->clear();
}
Cheers,
Maxime