Symfony Doctrine ManyToMany add custom join field - symfony

I have two entites : Cart and Item, the relation is configured with ManyToMany because a cart can have multiple items, and a items can be in multiple carts.
So I have a link table item_cart with item_id and cart_id.
How can I work with quantity with this ? For example if I need to add 800 items with id = 2 to the cart with id = 5 ?
Is this possible to add a field quantity in the link table ?
Thanks for help.

You can do this by making the relationship itself to an entity. This entity would be called CartItem or CartItemLink.
The association changes from ManyToMany between Cart and Item to two associations ManyToOne and OneToMany:
Cart - ManyToOne - CartItem - OneToMany - Item
Now you can add additional fields to your CartItem, like a $quantity field as mentioned in your question.
So this would look something like this:
The CartItem:
class CartItem {
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* #var Cart
* #ORM\ManyToOne(targetEntity="Application\Entity\Cart", inversedBy="cartItems")
* #ORM\JoinColumn(name="cart_id", referencedColumnName="id")
*/
private $cart;
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* #var Item
* #ORM\ManyToOne(targetEntity="Application\Entity\Item", inversedBy="cartItems")
* #ORM\JoinColumn(name="item_id", referencedColumnName="id")
*/
private $item;
/**
* #var int
* #ORM\Column(type="integer", nullable=false)
*/
private $quantity;
//.. setters + getters
}
The Cart:
class Cart {
/**
* #var integer
* #ORM\Id
* #ORM\Column(type="integer", nullable=false)
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/** ONE-TO-MANY BIDIRECTIONAL, INVERSE SIDE
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Application\Entity\CartItem", mappedBy="cart")
*/
private $cartItems;
//.. setters + getters
}
The Item:
class Item {
/**
* #var integer
* #ORM\Id
* #ORM\Column(type="integer", nullable=false)
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/** ONE-TO-MANY BIDIRECTIONAL, INVERSE SIDE
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Application\Entity\CartItem", mappedBy="item")
*/
private $cartItems;
//.. setters + getters
}
I didn't add an id to CartItem because it can have either a composite key ($item_id + $cart_id) or a natural key and that I leave up to you.
Don't forget to initialize your $cartItems ArrayCollection inside the constructor of Item and Cart.

Related

Doctrine|ORM|Symfony: Is possible relation to Interface or multiple entities

Simple example:
I've got two users Admin and Client (both implements UserInterface) and Cart - three entity classes at a. Admin and Client can have his own carts. How to configure/resolve Cart entity relation to have method 'getUser()' which returns Admin or Client user?
Maybe I can have column user_id and second column with user entity name in Cart (something similar as DiscriminatorMapping can do)?
class Admin implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Collection
* #ORM\OneToMany(targetEntity="Cart", mappedBy="???")
*/
private $carts;
....
class Client implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Collection
* #ORM\OneToMany(targetEntity="Cart", mappedBy="???")
*/
private $carts;
....
class Cart
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var UserInterface
* #ORM\ManyToOne(targetEntity="UserInterface", ???)
*/
private $user;
....
I tried this Doctrine feature, also tried DisciminatorMapping and composite keys (join by multiple columns) option with no luck.
Any help?
I think you were almost there. DiscriminatorMapping is probably the way to go. However, you need to bind Client and Admin to a Parent class. So consider this hierarchy:
User (parent class)
Admin (extends User)
Client (extends User)
Then in your Cart entity you bind the relation to the User entity.

NOT NULL if value is true in another column

I'm creating my entities and I want to create an entity with two columns that need to have a specific constraint. If addressId is defined, then extAddressId can be null (and it has to be null).
/**
* #ORM\Entity()
* #ORM\Table(name="widgets")
*/
class Widget
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
*/
private $addressId;
/**
* #ORM\Column(type="integer")
*/
private $extAddressId;
}
I know how to do it with SQL but not with doctrine.
CREATE TABLE widgets
(
id integer,
addressId integer,
extAddressId integer,
CONSTRAINT if_addressId_then_extAddressId_is_not_null
CHECK ( (NOT addressId) OR (extAddressId IS NOT NULL) )
);
According to the documentation you can add check constraints like this :
/**
* #ORM\Column(type="integer", options={"check":"[your check condition]"})
*/
private $addressId;
/**
* #ORM\Column(type="integer", options={"check":"[your check condition]"})
*/
private $extAddressId;
Haven't tested myself.

Symfony: ManyToMany table extra columns

I have a many to many table for User and House, called user_house. Instead of just two columns: user_id and house_id, i want to add 3 more: eg action, created_at, updated_at. How can I do this?
I cannot find any relevant docs on this.
The following just creates a separate table with two columns in it.
class User extends EntityBase
{
...
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\House")
*/
protected $action;
Basically, what I want to achieve is:
in the user_house table the combination of user_id, house_id, action should be unique.
when a user clicks a "view" on a house, user_house table gets updated with some user_id, some house_id, view, now(), now()
when a user clicks a "like" on a house, user_house table gets updated with some user_id, some house_id, like, now(), now()
when a user clicks a "request a call" on a house, user_house table gets updated with some user_id, some house_id, contact, now(), now()
Could someone point me in the right direction? Thanks!
You need to break your ManyToMany relation to OneToMany and ManyToOne by introducing a junction entity called as UserHasHouses, This way you could add multiple columns to your junction table user_house
User Entity
/**
* User
* #ORM\Table(name="user")
* #ORM\Entity
*/
class User
{
/**
* #ORM\OneToMany(targetEntity="NameSpace\YourBundle\Entity\UserHasHouses", mappedBy="users",cascade={"persist","remove"} )
*/
protected $hasHouses;
}
House Entity
/**
* Group
* #ORM\Table(name="house")
* #ORM\Entity
*/
class House
{
/**
* #ORM\OneToMany(targetEntity="NameSpace\YourBundle\Entity\UserHasHouses", mappedBy="houses",cascade={"persist","remove"} )
*/
protected $hasUsers;
}
UserHasHouses Entity
/**
* UserHasHouses
* #ORM\Table(name="user_house")
* #ORM\Entity
*/
class UserHasHouses
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="NameSpace\YourBundle\Entity\House", cascade={"persist"}, fetch="LAZY")
* #ORM\JoinColumn(name="house_id", referencedColumnName="id")
*/
protected $houses;
/**
* #ORM\ManyToOne(targetEntity="NameSpace\YourBundle\Entity\User", cascade={"persist","remove"}, fetch="LAZY" )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id",nullable=true)
*/
protected $users;
/**
* #var \DateTime
* #ORM\Column(name="created_at", type="datetime")
*/
protected $createdAt;
/**
* #var \DateTime
* #ORM\Column(name="updated_at", type="datetime")
*/
protected $updatedAt;
//... add other properties
public function __construct()
{
$this->createdAt= new \DateTime('now');
}
}
have additional column in ManyToMany join table in Doctrine (Symfony2)

Error when persisting doctrine entity with OneToMany association

New entities in a collection using cascade persist will produce an Exception and rollback the flush() operation. The reason is that the "UserGroupPrivilege" entity has identity through a foreign entity "UserGroup".
But if the "UserGroupPrivilege" has its own identity with auto generated value the code works just fine, and I don't want that I want the identity to be a composite key to enforce validation. here is my code:
Entity UserGroup:
class UserGroup
{
/**
* #var integer
*
* #ORM\Column(type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(type="boolean", nullable=false)
* #Type("integer")
*/
private $active;
/**
* #ORM\OneToMany(targetEntity="UserGroupPrivilege", mappedBy="userGroup", cascade={"persist"})
*/
private $privileges;
Entity UserGroupPrivilege:
class UserGroupPrivilege
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer", nullable=false)
*
*/
private $privilegeId;
/**
* #var UserGroup
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="UserGroup", inversedBy="privileges")
* #ORM\JoinColumn(name="userGroupId", referencedColumnName="id")
*/
private $userGroup;
/**
* #var string
* #ORM\Column(type="string", nullable=false)
*/
private $name;
/**
* #var string
* #ORM\Column(type="string", nullable=false)
*/
private $value;
Controller:
$userGroup = new UserGroup();
$userGroupPrivilege = new UserGroupPrivilege();
userGroupPrivilege->setUserGroup($userGroup)
->setName($arrPrivilege['name'])
->setValue($arrPrivilege['value'])
->setPrivilegeId($arrPrivilege['privilegeId']);
$userGroup->addPrivilege($userGroupPrivilege);
$data = $repo->saveUserGroup($userGroup);
return $data;
Repository:
$em = $this->getEntityManager();
$em->persist($userGroup);
$em->flush();
I get the following error:
Entity of type UserGroupPrivilege has identity through a foreign entity UserGroup, however this entity has no identity itself. You have to call EntityManager#persist() on the related entity and make sure that an identifier was generated before trying to persist 'UserGroupPrivilege'. In case of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this means you have to call EntityManager#flush() between both persist operations.
Error message is pretty self explanatory. To relate UserGroupPrivilege to UserGroup, UserGroup must have it's ID set. However, since you've just created both entities it has no id because it hasn't been persisted to database yet.
In your case :
$em = $this->getEntityManager();
$em->persist($userGroup);
$em->persist($userGroupPrivilege);
$em->flush();
Can you "enforce validation" with unique constraint:
/**
* #Entity
* #Table(uniqueConstraints={#UniqueConstraint(name="ugppriv_idx", columns={"priviledgeId", "userGroup"})})
*/
class UserGroupPriviledge
{
...

M:N relation with more fields

So far the relations M:N I've built are simple intermediate tables where Doctrine does not need to create an entity for this table.
I have two entities Product and ingredient, they have a relationship M:N easily describe with Doctrine as follows. but the real problem is when i need store a amount field in the relation (I need to list the ingredients and also the amount).
How can solve this?
class Product {
//...
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Ingredient", inversedBy="product")
* #ORM\JoinTable(name="product_ingredient",
* joinColumns={
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="ingredient_id", referencedColumnName="id")
* }
* )
*/
private $ingredient;
//...
class Ingredient {
// ...
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Product", mappedBy="ingredient")
*/
private $product;
// ...
You can't do it without intermediate entity really, that's why doctrine docs says that ManyToMany relationships are rare.
It's also the easiest thing to do, just add RecipeItem entity which will store information about Ingredient and amount and link it with relationship of ManyToOne to Product
Edit
Since I was asked to provide an example:
class Product {
//...
/**
* #ORM\OneToMany(targetEntity="RecipeItem", mappedBy="product")
*/
private $ingredients;
//...
class RecipeItem {
// ...
/**
* #ManyToOne(targetEntity="Product", inversedBy="ingredients")
**/
private $product;
/**
* #ManyToOne(targetEntity="Ingridient")
**/
private $ingredient;
/**
* #Column(type="decimal")
**/
private $amount;
}
class Ingredient {
// Don't use bidirectional relationships unless you need to
// it impacts performance
}
Now having a product you can simply:
foreach($product->getIngridients() as $item){
echo "{$item->getAmount()} of {$item->getIngridient()->getName()}";
}

Resources