Doctrine multi level inheritance order pblm - doctrine-orm

When using class table inheritance with multiple levels in doctrine :
Account->User->Dealer
(Dealer inherits from User, User from Account)
Account has the discriminator column and mapping. When persisting a new Dealer entity I get a foreign key error because there is no row in the User table.
So the order in which the insert statements are executed is:
1) Insert into Account
2) Insert into Dealer (which causes the error)
3) insert into User
Can someone help me thanks in advance

this is my configuration :
/**
* #Table(name="Account")
* #Entity
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"Account" = "Default_Model_Account","User" = "Default_Model_User","Dealer" = "Default_Model_Dealer"})
*/
class Default_Model_Account {
/**
* #id #column(name="accountID",type="integer")
* #GeneratedValue(strategy="AUTO")
*/
protected $accountID;
/**
* #column(name="email",type="string")
*
*/
protected $email;
}
/**
* #Table(name="User")
* #Entity
*/
class Default_Model_User extends Default_Model_Account{
/**
* #column(name="firstName",type="string")
*/
protected $firstName;
}
/**
* #Entity
* #Table(name="Dealer")
*
*/
class Default_Model_Dealer extends Default_Model_User{
/**
* #column(name="type",type="string")
*/
protected $type;
}

Related

How to create other columns inside the intermediary table of a many-to-many relationship in Symfony?

There are two entities having a many-to-many relationship :
/**
* #ORM\Entity(repositoryClass=KbFavorisRepository::class)
*/
class KbFavoris
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
...
/**
* #var \Doctrine\Common\Collections\Collection|KbCollection[]
*
* #ORM\ManyToMany(targetEntity="KbCollection", mappedBy="kb_favoris",cascade={"persist"})
*/
protected $collection;
...
}
/**
* #ORM\Entity(repositoryClass=KbCollectionRepository::class)
*/
class KbCollection
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #JMS\Groups({"list_collection"})
*/
private $id;
...
/**
* #var KbFavoris
* #ORM\ManyToMany(targetEntity="KbFavoris",inversedBy="collection", cascade={"remove"})
* #ORM\JoinTable(
* name="kb_favoris_collection",
* joinColumns={
* #ORM\JoinColumn(name="collection_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="favoris_id", referencedColumnName="id")
* }
* )
*/
private $kb_favoris;
...
}
How to add another column inside the intermediary table of the many-to-many relationship ?
If you want to add some extra fields, you need to create another entity;
Ex:
class KbFavoris
{
// OneToMany here to KbFavorisKbCollection, is array|collection
}
class KbCollection
{
// OneToMany here to KbFavorisKbCollection , is array|collection
}
And the new class :
class KbFavorisKbCollection
{
// ManyToOne here to KbFavoris (represent one KbFavoris)
// ManyToOne here to KbCollection (represent one KbCollection )
}

Doctrine Single Table Inheritance and bidirectional OneToMany: columns not being generated

The data model:
The entities:
Pet:
/**
* #ORM\Entity
* #ORM\Table(name="pet")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="pet_type", type="string")
*/
abstract class Pet
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\OneToMany(
* targetEntity="Collar",
* mappedBy="pet",
* cascade={"persist", "remove"},
* orphanRemoval=TRUE
* )
* #ORM\JoinColumn(name="collars", referencedColumnName="id")
*/
protected $collars;
/**
* #ORM\Column(type="integer")
*/
protected $age;
}
Cat:
/**
* #ORM\Entity
* #ORM\Table(name="cat")
*/
class Cat extends Pet
{
/**
* #ORM\Column(type="decimal")
*/
private $purringDecibels;
}
Collar:
/**
* #ORM\Entity
* #ORM\Table(name="collar")
*/
class Collar
{
/**
* #ORM\Id
* #ORM\Column(name="id", type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Pet", inversedBy="collars")
* #ORM\JoinColumn(name="pet", referencedColumnName="id", nullable=false)
*/
private $pet;
/**
* #ORM\Column(type="string", length="255")
*/
private $color;
}
The problem:
The generated tables are fine except pet: It lacks the collars column, therefore the bidirectionality is broken. While the pet ids do get stored on the collar table, I cannot fetch collars from a pet (i.e. $pet->getCollars()) as it always returns an empty collection.
What am I missing out here?
PS:
The validate console helper says all (mapping & db) is OK.
Yes, I have my getters and setters (i.e. adders and removers)
I know about the infamous performance impact of the combination between a CTI and this kind of relationship.
You do not need to have a "collars" column on the pet table. It is a one-to-many relationship, so the only table that need to "know" about pets are the collar table.
A bidirectional relationship does not mean you need two columns on two tables.
Your map seems correct.

Symfony2 Form with Doctrine Relationship Persistence Issue

So I have 3 entities with their properties.
A USER which has MANY USERSKILLS with each ONE having a single corresponding SKILL
Here are the entities:
/**
* #ORM\Table(name="skills")
* #ORM\Entity()
*/
class Skill
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=30, unique=true)
*/
private $name;
/**
* #ORM\Column(name="active", type="boolean")
* #var bool
*/
private $active = false;
/**
* #ORM\OneToMany(targetEntity="UserSkill", mappedBy="skill")
*/
private $userSkills;
}
/**
* #ORM\Table(name="user_skill")
* #ORM\Entity()
*/
class UserSkill
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="skills")
* #var User
*/
private $user;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Skill", inversedBy="userSkills")
* #var Skill
*/
private $skill;
/**
* #ORM\Column(type="integer")
* #var int
*/
private $level = 0;
}
/**
* StartupDriven\UserBundle\Entity\User
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="StartupDriven\UserBundle\Entity\UserRepository")
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #ORM\OneToMany(targetEntity="UserSkill", mappedBy="user")
* #var ArrayCollection
*/
private $skills;
}
They are created using 2 symfony form objects which works fantastically.
The problem is when I go to persist these objects in the controller.... I get an error
$form->handleRequest($request);
if ($form->isValid()) {
/**
* #var User $user
*/
$user = $form->getData();
/**
* #var UserSkill $userSkill
*/
foreach ($user->getSkills() as $userSkill) {
// No updating skills... Only new ones.
if (!$userSkill->getId()) {
// Check skill name match.
if ($matched_skill = $em->getRepository('StartupDrivenUserBundle:Skill')->findOneBy(array('name' => $userSkill->getName()))) {
$userSkill->setSkill($matched_skill);
}
else {
// No match. Create new generic skill.
$em->persist($userSkill->getSkill());
// THE ERROR HAPPENS ON THE FOLLOWING LINE!
$em->flush();
}
// Set the User
$userSkill->setUser($user);
// Persist the user skill.
$em->persist($userSkill);
$em->flush();
}
}
$em->persist($user);
$em->flush();
Error Message:
A new entity was found through the relationship 'StartupDriven\UserBundle\Entity\User#skills' that was not configured to cascade persist operations for entity: StartupDriven\UserBundle\Entity\UserSkill#0000000053e8628e00007f7f2fd56e1f. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'StartupDriven\UserBundle\Entity\UserSkill#__toString()' to get a clue.
I have tried every combination of persist & flush that I can think of. I have tried the cascade option above (which gives a different error that the "user id which is required by the primary key rules is not set")... I am completely lost. It seems like such a simple relationship... Where am I going wrong?

Doctrine2: Many-To-Many with extra columns in reference table (add record)

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 many Cart
A Cart can contains several Product
The Product in a Cart are order by a position
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 2
I 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
Well many to many association with extra parameters can be implemented by using a third intermediate entity. You have the right approach, but wrong associations defined. Here's how it should be.
Taking your 3 entities Product, Cart, CartProducts
Cart should have a one-to-many relationship with CartProducts
CartProducts should have many-to-one relationship to Product and Many-to-one association with Cart
So you first initialize a Cart, and add products to Cart like this:
For every Product
initialize a CartProduct with the Product and Cart and other extra parameters you need.
Add it to the Cart

Doctrine2: Why is Mapping not detecting my composite keys

I have two tables/entities client and site that have a many to many relationship combined by a join table client_site. Here are how my entities are setup.
Here is the client table entity
/**
* #Entity
* #Table(name="client")
*/
class Client
{
/**
* #Id #Column(type="bigint")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ManyToMany(targetEntity="Site", inversedBy="clients")
* #JoinTable(name="client_site",
* joinColumns={#JoinColumn(name="c_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="s_id", referencedColumnName="id")}
* )
*/
private $sites;
And the site table entity
/**
* #Entity
* #Table(name="site")
*/
class Site
{
/**
* #Id #Column(type="bigint")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ManyToMany(targetEntity="Client", mappedBy="sites")
*/
private $clients;
This is the client_site table entity
/**
* #Entity
* #Table(name="client_site",indexes={#index(name="FK_client_site",columns={"c_id"}),#index(name="FK_client_site_2",columns={"s_id"})})
*/
class ClientSite
{
/**
* #Id
* #ManyToOne(targetEntity="Client", inversedBy="ClientSite")
*/
private $client;
/**
* #Id
* #ManyToOne(targetEntity="Site", inversedBy="ClientSite")
*/
private $site;
This is the query I am trying to run
$query = Zend_Registry::get('em')
->createQuery('SELECT c, s
FROM Application\Models\Client c
JOIN c.sites s
WHERE c.is_active = 1');
$clients = $query->getResult();
And this is my error
No identifier/primary key specified for Entity 'Application\Models\ClientSite'. Every Entity must have an identifier/primary key.
I put the #Id on both fields in the ClientSite entity, as they are the composite primary keys for my joiner table. Can this not be done in Doctrine2? If it can't, what are my alternative options.
If you CAN do this, what have I done incorrectly?
This isn't currently supported by Doctrine 2.
However, they are working on adding support for this in an experimental branch. Apparently this might be included in the 2.1 release if it works without issues.