I have a three entity:
class User
{
#ORM\OneToMany(targetEntity="Conversation", mappedBy="sender", cascade={"remove"})
private $send_messages;
}
///
class Conversation
{
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="send_messages")
* #ORM\JoinColumn(name="sender_id", referencedColumnName="id", onDelete="SET NULL")
*/
private $sender;
}
///
class Message
{
/**
* #ORM\OneToMany(targetEntity="Conversation", mappedBy="message")
*/
private $conversations;
/**
* #ORM\PreRemove
*/
function onPreRemove()
{
// how remove parent relationship ????
}
}
When I delete the User, Conversation deleted by CASCADE. Question - how to remove and Message when deleted Coversation is last relation of Message.
I think what you're looking for is Doctrine2 "Orphan removal"
If an Entity of type A contains references to privately owned Entities B then if the reference from A to B is removed the entity B should also be removed, because it is not used anymore.
Related
I have an entity User with lots of feature built for it.
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity("email", message="Email already in use")
* #ORM\HasLifecycleCallbacks
* #Table(name="users")
*/
class User implements UserInterface
{
/* variables + getter & setter */
}
This entity is good as is for most of my User.
However, a few of them will have a special ROLE, ROLE_TEACHER.
With this role, I need to store a lot of new variables specially for them.
If I create a new entity Teacher, doctrine creates a new table with every User's data + the Teacher's data.
/**
* #ORM\Entity(repositoryClass="App\Repository\TeacherRepository")
* #Table(name="teachers")
*/
class Teacher extends User
{
/**
* #ORM\Column(type="string", length=64, nullable=true)
*/
protected $test;
public function __construct()
{
parent::__construct();
}
}
What I want, is for Teacher & User to share the users table and have the teachers table only store the extra data. How could I achieve that ?
This is more of system design problem than implementation problem. as #Gary suggested you can make use of Inheritance Mapping which can have Performance issues, I'd rather suggest re think your schema and make use of database normalization techniques to break up your data into more manageable entities.
You can have User entity :
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity("email", message="Email already in use")
* #ORM\HasLifecycleCallbacks
* #Table(name="users")
*/
class User implements UserInterface
{
/* variables + getter & setter */
/**
* One user has many attibute data. This is the inverse side.
* #OneToMany(targetEntity="UserData", mappedBy="data")
*/
private $data;
}
With other UserData Entity with OneToMany relationship :
/**
* #ORM\Entity(repositoryClass="App\Repository\UserDataRepository")
* #Table(name="user_data")
*/
class UserData
{
/* variables + getter & setter */
#ORM\Id()
private $id;
/**
* Many features have one product. This is the owning side.
* #ManyToOne(targetEntity="User", inversedBy="data")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/**
* #ORM\Column(type="string")
*/
private $attribute;
/*
* #ORM\Column(name="value", type="object")
*/
private $value;
}
Now you can have list of user attributes without requiring specific structure to each role. It's scalable and arbitrary.
You can also define same Relation with TeacherData, StudentData or UserProfile Entities with foreign keys and branch your application logic according to the roles. Key is to break data into their separate domains and keep common data in one table. Load related data by querying related entity, this increases readability and makes it easy to break complex structure into manageable codebase.
I have two entities for example:
class Dog
{
/**
* #var House
*
* #ORM\ManyToOne(targetEntity="House")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="house_id", referencedColumnName="id")
* })
*/
private $house;
}
class House
{
/**
* #var ArrayCollection|null
* #ORM\ManyToMany(targetEntity="Dog",cascade={"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="dog_id", referencedColumnName="id", nullable=true)
* })
*/
protected $dog;
}
I need to throw an event if field house in Entity Dog was update (set or remove) then add or remove field dog in Entity House.
Can anyone show me how do this ?
Doctrine will do this for you but depending on the cascade option. But your annotations are not correct. In the Dog entity you have annotation for a ManyToOne and in the House entity for a ManyToMany relation. But you should choose between
ManyToOne - OneToMany
ManyToMany - ManyToMany
Take a look into the Doctrine's association mapping to read about all the types of associations and how to define them.
If you are using Symfony (4 or 5) you should use the commandline make tool to add
properties and methods with all the annotations, even for relations.
bin/console make:entity Dog
Type relation when asked for the Field type and you will have to answer some additional questions.
You must call $dog->setHouse($this); from the addDog method. If you used the commandline then below class House would be generated for you.
class House
{
// ...
/**
* #ORM\OneToMany(targetEntity="App\Entity\Dog", mappedBy="house")
*/
private $dogs;
public function __construct()
{
$this->dogs = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
/**
* #return Collection|Dog[]
*/
public function getDogs(): Collection
{
return $this->dogs;
}
public function addDog(Dog $dog): self
{
if (!$this->dogs->contains($dog)) {
$this->dogs[] = $dog;
$dog->setHouse($this); // <-- here you go
}
return $this;
}
public function removeDog(Dog $dog): self
{
if ($this->dogs->contains($dog)) {
$this->dogs->removeElement($dog);
// set the owning side to null (unless already changed)
if ($dog->getHouse() === $this) {
$dog->setHouse(null);
}
}
return $this;
}
}
Same thing counts for removeDog() method.
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.
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.
I have 3 entity : Invoice, InvoiceItemService, Asset.
First, I create an Invoice and an InvoiceItemService, and I link them together with a ManyToOne relation on InvoiceItemService side (so InvoiceItemService is the owner side).
Then, I can create an Asset, from the Invoice object. An asset is kind of a copy of an Invoice, with a negative total. When I create an Asset, I link the Invoice's InvoiceItemService to the Asset too, with a ManyToOne relation between InvoiceItemService and Asset.
When I delete an Invoice, everything works fine, Invoice, Asset and InvoiceItemService are deleted with a cascade={"remove"} option.
When I delete an Asset, I would like only the asset to be deleted. But I get this foreign key error.
Here are my entities :
class Invoice
{
/**
* #ORM\OneToMany(targetEntity="Evo\BackendBundle\Entity\Asset", mappedBy="invoice", cascade={"remove"})
*/
protected $assets;
/**
* #ORM\OneToMany(targetEntity="Evo\BackendBundle\Entity\InvoiceItemService", mappedBy="invoice", cascade={"persist", "remove"})
*/
protected $services;
}
class Asset
{
/**
* #ORM\ManyToOne(targetEntity="Evo\BackendBundle\Entity\Invoice", inversedBy="assets")
* #ORM\JoinColumn(nullable=false)
* #Exclude
*/
protected $invoice;
/**
* #ORM\OneToMany(targetEntity="Evo\BackendBundle\Entity\InvoiceItemService", mappedBy="asset")
*/
protected $services;
}
class InvoiceItemService extends InvoiceItem
{
/**
* #ORM\ManyToOne(targetEntity="Evo\BackendBundle\Entity\Invoice", inversedBy="services")
* #ORM\JoinColumn(nullable=false, onDelete="CASCADE")
* #Type("Evo\BackendBundle\Entity\Invoice")
* #Exclude
*/
protected $invoice;
/**
* #ORM\ManyToOne(targetEntity="Evo\BackendBundle\Entity\Asset", inversedBy="services")
* #ORM\JoinColumn(nullable=true, onDelete="SET NULL")
* #Type("Evo\BackendBundle\Entity\Asset")
* #Exclude
*/
protected $asset;
}
As Cerad mentioned in the comments above, you'll need to implement this behavior yourself.
Since you'll need access to the entity manager, you can't do this with a simple lifecycle callback on the Asset entity itself. Instead, you'll need to register a service to listen for the event and perform the action at that point.
Example implementation
app/config.yml
services:
your.bundle.association.manager:
class: Your\Bundle\Model\AssociationManager
tags:
- { name: doctrine.event_listener, event: preRemove }
And then, the service class itself (unstested - might have bugs)
namespace Your\Bundle\Model;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\EntityManager;
use Your\Bundle\Entity\Asset;
class AssociationManager
{
public function preRemove(LifecycleEventArgs $args)
{
if ($entity instanceof Asset)
{
$this->removeAssetAssociations($asset, $args->getEntityManager());
return;
}
}
protected function removeAssetAssociations(Asset $asset, EntityManager $em)
{
foreach ($asset->getServices() as $invoiceItemService)
{
$invoiceItemService->asset = null;
$em->persist($invoiceItemService);
}
}
}
Hope this helps.