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.
Related
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.
my question is how to delete entity on the inverse side without going through every association and delete it manually.
<?php
/** #Entity */
class User
{
// ...
/**
* #OneToMany(targetEntity="Address", mappedBy="user")
*/
private $addresses;
// ...
public function __construct() {
$this->addresses = new ArrayCollection();
}
}
/** #Entity */
class Address
{
// ...
/**
* #ManyToOne(targetEntity="User", inversedBy="features")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
// ...
}
In this example, Address is the owning side, so I can't delete User because foreign key validation will fail. I have to delete Address and then delete User. If I have 10 relationships like these the delete process is painful.
I can create ManyToMany relationship, but this way the Address entity will have users not user and I want addresses to have only one user.
What is the best way to do this?
I hope it's helpful.
Just add cascade to inverse side entity.
/** #Entity */
class User
{
// ...
/**
* #OneToMany(targetEntity="Address", mappedBy="user", cascade={"persist", "remove"})
*/
private $addresses;
// ...
public function __construct() {
$this->addresses = new ArrayCollection();
}
/*
* #return ArrayCollection
*/
public function getAddresses (){
return $this->addresses:
}
/*
* #pram Address $address
*/
public function setAddresses (Address $address){
$this->addresses->add ($address);
}
}
i have the following problem :
I extend an entity tableA in a tableB entity and i want to override the #assert/notblank annotation on a field.
Class tableA{
...
/**
* #ORM\Column(type="string", length=4)
* #Assert\NotBlank(message="please.enter.a.value")
*/
protected $myfield;
}
The extended class :
Class tableB extends tableA{
...
/**
* #ORM\Column(type="string", length=4, nullable=true)
*/
protected $myfield;
}
The not blank constraints of TableA is still active when i make a form using tableB.
Any idea ?
I have tried with #ORM\AttributeOverride annotation but it dont works...
It seems indeed Symfony Validation component fails to understand what's going on.
A way around isto put the Validation constraint on the getter:
Class tableA{
/**
* #ORM\Column(type="string", length=4)
*/
protected $myfield;
}
/**
* #Assert\NotBlank(message="please.enter.a.value")
*/
public function getMyfield()
{
// ...
}
And in the extended class:
Class tableAB extends tableA{
public function getMyfield()
{
// ...
}
I have a #ManyToMany relation with two entities.
I have (short code)
class Photo
{
/**
* #ORM\ManyToMany(targetEntity="Application\Sonata\UserBundle\Entity\User", mappedBy="photos")
*/
protected $users;
}
class User
{
/**
* #ORM\ManyToMany(targetEntity="MyBundle\PhotoBundle\Entity\Photo", inversedBy="users", cascade={"persist"})
* #ORM\JoinTable(name="user_photos")
*/
protected $photos;
}
php app/console doctrine:schema:validate
-> [Mapping] FAIL - The entity-class 'MyBundle\PhotoBundle\Entity\Photo' mapping is invalid:
* The association MyBundle\PhotoBundle\Entity\Photo#users refers to the owning side field Application\Sonata\UserBundle\Entity\User#photos which does not exist.
I looked in stack and i try somethings but i still have the problem.
Try this
class Photo
{
/**
* #ORM\ManyToMany(targetEntity="Application\Sonata\UserBundle\Entity\User", mappedBy="photo")
*/
protected $users;
}
class User
{
/**
* #ORM\ManyToMany(targetEntity="MyBundle\PhotoBundle\Entity\Photo", inversedBy="users", cascade={"persist"})
* #ORM\JoinTable(name="user_photos")
*/
protected $photos;
}
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.