Here's my situation:
Framework Symfony 4, using Doctrine(version).
I have three entities : A, B and C.
Entity A has a OneToMany relationship with B, and a OneToMany relationship with C.
Entity B has a OneToMany relationship with C.
In a form, I have a collectionForm about entity C embedded in a collectionForm about entity B.
Adding entities B & C is optional in the form.
However, when trying to add entity C (and therefore entity B), seeing as entity C has two parents (A & B), doctrine fails to know in which order to persist those, and gives the following error :
A new entity was found through the relationship 'App\Entity\EntityC#entityB' that was not configured to cascade persist operations for entity: App\Entity\EntityB#0000000010cd7f460000000077359844. 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 'App\Entity\EntityB#__toString()' to get a clue.
How do I tell doctrine to persist entity A first, then B and then C in this case ?
So far, I've tried removing the OneToMany relationship between B and C, without any luck, and adding a ManyToOne relationship in entityC for entity B.
I feel if I could bypass this relationship between B and C this could work, but I don't know how.
EntityA :
class entityA extends BaseEntityA {
/**
* #ORM\OneToMany(targetEntity="App\Entity\EntityB", mappedBy="entityA", cascade={"persist"})
* #ORM\JoinColumn(name="id", referencedColumnName="entityA_id", nullable=false)
*/
protected $entitiesB;
/**
* #ORM\OneToMany(targetEntity="App\Entity\EntityC", mappedBy="entityA", cascade={"persist"})
* #ORM\JoinColumn(name="id", referencedColumnName="entityA_id", nullable=false)
*/
protected $entitesC;
...
/**
* Add EntityB entity to collection (one to many).
*
* #param \App\Entity\EntityB $entityB
*
* #return \App\Entity\EntityA
*/
public function addEntityB(\App\Entity\EntityB $entityB)
{
$this->entitiesB[] = $entityB;
$entityB->setEntityA($this);
return $this;
}
/**
* Add EntityC entity to collection (one to many).
*
* #param \App\Entity\EntityC $entityC
*
* #return \App\Entity\EntityA
*/
public function addEntityC(\App\Entity\EntityC $entityC)
{
$this->entitiesC[] = $entityC;
$vehicle->setEntityA($this);
return $this;
}
Entity B :
class EntityB extends BaseEntityB {
/**
* #ORM\OneToMany(targetEntity="App\Entity\EntityC", mappedBy="entityB", cascade={"persist"})
* #ORM\JoinColumn(name="id", referencedColumnName="entityB_id", nullable=false)
*/
protected $entitiesC;
...
/**
* #param Collection $entitiesC
*
* #return $this
*/
public function setEntityC(Collection $entitiesC)
{
$this->entitiesC = $entitiesC;
return $this;
}
/**
* Add EntityC entity to collection (one to many).
*
* #param \App\Entity\EntityC $entityC
*
* #return \App\Entity\EntityB
*/
public function addEntityC(\App\Entity\EntityC $entityC)
{
$this->entitiesC[] = $entityC;
$entityC->setEntityB($this);
$entityC->setEntityA($this->getEntityA());
// Things I tried to remove the relationship between B and C
// $this->getEntityA()->addEntityC($entityC);
// $entityC->setEntityB($this);
return $this;
}
Entity C :
class EntityC extends BaseEntityC {
// Somtehing I tried at some point
// /**
// * #ORM\ManyToOne(targetEntity="EntityB", inversedBy="entitiesC", cascade={"persist"})
// * #ORM\JoinColumn(name="entityB_id", referencedColumnName="id")
// */
// protected $entityB;
...
}
I would like to persist this entities in the right order (A then B then C), using only one form if possible.
Related
I want to use an optional ManyToMany relation between Ordo_soins_perfusion and Ordo_soins_medicament entities.
class Ordo_soins_perfusion
{
/**
* #ORM\ManyToMany(targetEntity="Ordo_soins_medicament",cascade={"persist"})
*#ORM\JoinTable(name="ordo_soinperf_soinmedoc",
* joinColumns={#ORM\JoinColumn(name="Ordo_soins_perfusion_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="Ordo_soins_medicament_id", referencedColumnName="id",nullable=true)})
*/
private $medoc;
class Ordo_soins_medicament
{
/**
* #var string
*
* #ORM\Column(name="medicament", type="string", length=255,nullable=true)
*/
private $medicament;
/**
* #var string
*
* #ORM\Column(name="quantite", type="string", length=50,nullable=true)
*/
private $quantite;
Now when i save a new Ordo_soins_perfusion object without filling the Ordo_soins_medicament form i found a new ligne created in the join table and in the Ordo_soins_medicament table.
How the add a Ordo_soins_medicament object only if not null
Thanks
First make sure the ManyToMany relationship is correct. Thereafter initialize an empty new ArrayCollection() on these properties, so relationships actually can be added by Doctrine.
public function __construct()
{
$this->medoc = new ArrayCollection();
}
The next step is to make sure the relationship is set properly, by adding getters/setters. In case of collections you could also use add.
public function addMedoc($item)
{
$item->setPerfusion($this);
$this->medoc->add($item);
}
Doctrine will handle the relationship/join table when you persist and flush the new entity.
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()}";
}
Hi i have that same question as here: Many-to-many self relation with extra fields? but i cant find an answer :/ I tried first ManyToOne and at the other site OneToMany ... but then i could not use something like
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
because there was some this problem:
This function is called, taking a User type $user variable and you then use the contains() function on $this->myFriends.
$this->myFriends is an ArrayCollection of Requests (so different type than User) and from the doctrine documentation about contains():
The comparison of two elements is strict, that means not only the value but also the type must match.
So what is the best way to solve this ManyToMany relationship with extra fields? Or if i would go back and set the onetomany and manytoone relationship how can i modify the hasFriend method? To example check if ID is in array collection of ID's.
EDIT: i have this table... and what i need is:
1. select my friends... and my followers ...check if i am friend with him or not. (because he can be friend with me and i dont have to be with him... like on twitter). I could make manytomany but i need extra fields like: "viewed" "time when he subscribe me" as you can see at my table.
And make query like this and then be able in twig check if (app.user.hasFriend(follower) or something like that)
$qb = $this->createQueryBuilder('r')
->select('u')
->innerJoin('UserBundle:User', 'u')
->Where('r.friend_id=:id')
->setParameter('id', $id)
->orderBy('r.time', 'DESC')
->setMaxResults(50);
return $qb->getQuery()
->getResult();
I was trying to have a many to many relationship with extra fields, and couldn't make it work either... The thing I read in a forum (can't remember where) was:
If you add data to a relationship, then it's not a relationship anymore. It's a new entity.
And it's the right thing to do. Create a new entity with the new fields, and if you need it, create a custom repository to add the methods you need.
A <--- Many to many with field ---> B
would become
A --One to many--> C (with new fields) <-- One to many--B
and of course, C has ManyToOne relationships with both A and B.
I searched everywhere on how to do this, but in the end, it's the right thing to do, if you add data, it's no longer a relationship.
You can also copy what contains usually do, or try to overwrite it in a custom repository, to do whatever you need it to do.
I hope this helps.
I'm adding another answer since it has nothing to do with my original answer. Using the new info you posted, I'm calling the table/entity you posted "Follower". The original entity, "User".
What happens if you create the following associations:
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\UserBundle\Entity\User
*
* #ORM\Table()
* #ORM\Entity
*/
class User
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeduser")
*/
protected $followers;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeeuser")
*/
protected $followees;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function __construct()
{
$this->followers = new \Doctrine\Common\Collections\ArrayCollection();
$this->followees = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add followers
*
* #param Acme\FollowerBundle\Entity\Follower $follower
*/
public function addFollower(\Acme\FollowerBundle\Entity\Follower $follower)
{
$this->followers[] = $follower;
}
/**
* Add followees
*
* #param Acme\FollowerBundle\Entity\Follower $followee
*/
public function addFollowee(\Acme\FollowerBundle\Entity\Follower $followee)
{
$this->followees[] = $followee;
}
/**
* Get followers
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowers()
{
return $this->followers;
}
/**
* Get followees
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowees()
{
return $this->followees;
}
}
namespace Acme\FollowerBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\FollowerBundle\Entity\Follower
*
* #ORM\Table()
* #ORM\Entity
*/
class Follower
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $followeduser;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id")
*/
protected $followeeuser;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set followeduser
*
* #param Acme\UserBundle\Entity\User $followeduser
*/
public function setFolloweduser(\Acme\UserBundle\Entity\User $followeduser)
{
$this->followeduser = $followeduser;
}
/**
* Get followeduser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweduser()
{
return $this->followeduser;
}
/**
* Set followeeuser
*
* #param Acme\UserBundle\Entity\User $followeeuser
*/
public function setFolloweeuser(\Acme\UserBundle\Entity\User $followeeuser)
{
$this->followeeuser = $followeeuser;
}
/**
* Get followeeuser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweeuser()
{
return $this->followeeuser;
}
}
I'm not sure if this would do the trick, I really don't have much time to test it, but if it doesn't, I thnk that it's on it's way. I'm using two relations, because you don't need a many to many. You need to reference that a user can have a lot of followers, and a follower can follow a lot of users, but since the "user" table is the same one, I did two relations, they have nothing to do with eachother, they just reference the same entity but for different things.
Try that and experiment what happens. You should be able to do things like:
$user->getFollowers();
$follower->getFollowedUser();
and you could then check if a user is being followed by a follower whose user_id equals $userThatIwantToCheck
and you could search in Followers for a Follower whose user = $user and followeduser=$possibleFriend
I have got two entities associated with OneToOne association.
This is how it all looks like:
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="a")
*/
class A
{
....
/**
* #ORM\OneToOne(targetEntity="B", cascade={"all"}, orphanRemoval=true)
* #ORM\JoinColumn(name="b_id", referencedColumnName="id", nullable=true)
*/
private $b;
}
/**
* #ORM\Entity
* #ORM\Table(name="b")
*/
class B
{
....
/**
* #ORM\Column(type="string", length=20)
*/
protected $type;
/**
* #ORM\Column(type="integer")
*/
protected $aId;
}
What I am trying to do is to crate lifeCycleCallback preUpdate on A class.
While calling preUpdate A class already has its on ID. I have tried to do something like this:
/**
* #ORM\PreUpdate
*/
public function update()
{
if (is_null($this->getB()))
$this->b = new B();
$this->b->setAId($this->id)->setType('A'/* classname */);
}
B class has to store classname in type filed and object id in aId field. I cannot use foreign keys here.
The problem is that the above update function returns this error:
Notice: Undefined index: 000000006914b7950000000033b7b784 in C:...\vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php line 2735
Any ideas what this error means and how to solve it?
EDIT
This is how I fetch A object before passing it to edit form:
$this->getEntityManager()
->createQueryBuilder()
->select('a,b')
->from('MyBundle:A', 'a')
->leftJoin('a.b','b')
->where('a.id = :aId')
->setParameter('aId', $id)
->getQuery()->getSingleResult();
I do edit only A object (in form) and want to create associated B object on first A update and make some changes to this B object on every next update of A.
EDIT2
The above error no longer displays. However changes made to B object in #ORM\PreUpdate of A object are never persisted even when association to be has cascade={"all"}.
Here is the example:
/**
* inside A class !
*
* #ORM\PreUpdate
*/
public function update()
{
$this->lastModified = new \DateTime();
$this->getB()
->setSomething('something'); // <-- this is never stored to db by cascade
}
So, i have the following structure of entities:
/**
* #ORM\Entity
*/
class Group
{
/**
* Many-To-Many, Unidirectional
*
* #var ArrayCollection $permissions
*
* #ORM\ManyToMany(targetEntity="Permission")
* #ORM\JoinTable(name="group_has_permission",
* joinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="permission_id", referencedColumnName="id")}
* )
*/
protected $permissions;
public function __construct()
{
$this->permissions = new ArrayCollection();
}
}
/**
* #ORM\Entity
*/
class Permission {}
It's just an example, but i'm confused. I need another entity probably called "group_has_permission" with two fields: group_id and permission_id, right? Or am i wrong?
You don't need to create a new entity.
Doctrine will create for you a group table, a permission table & a join table in order to link a group to multiple permissions. This is transparent for you.