I have two entities linked together by a ManyToMany relationship in a Doctrine/MySQL project.
A Client entity:
class Client
{
[...]
/**
* #ORM\ManyToMany(targetEntity="ClientTag")
* #ORM\JoinTable(name="clients_tags")
*/
protected $tags;
}
And a ClientTag entity:
class ClientTag
{
[...]
/**
* #ORM\Column(type="string", length=45)
*/
protected $label;
/**
* #ORM\Column(type="string", length=7)
*/
protected $color;
}
So I have the ability to associate multiple clients to one tag, and vice-versa, great.
But I can't find a way to automatically remove a tag when there is no more clients referencing it.
I tried to use orphanRemoval on the ManyToMany annotation but it doesn't do what I thought.. Orphan removal should imply exactly what I described above but it removes the tag when the reference to its parent is removed, not considering other entities like I need to.
If a client removes a tag but this tag is still used by 2 other clients, I don't consider it "orphan" as it still has one or more entities referencing it.
Of course I could solve the case by doing a query and removing it myself if I don't find any parent, but I wonder if Doctrine or MySQL have a built in way to do this (that will be far more optimized) ?
Any idea?
Thanks for your help.
Officially orphanRemoval isn't supported for ManyToMany relations in doctrine.
http://docs.doctrine-project.org/en/latest/reference/annotations-reference.html#annref-manytomany
The orphan removal in this case is ambiguous.
You can either just understand the relations (the jointable entries) to the deleted entity as the orphans or the related entity.
From a database point of view it would be the jointable entries.
From an ORM point of view it's the related entities.
Thing is both ways are correct depending on the use case. For example in an Article <-> Category relation you would want to remove the article from all associated categories on deletion, but you wouldn't want to throw away the whole category just because it's empty at this moment.
I'm guessing that's the reason why Doctrine doesn't officially mention the orphanRemoval option for ManyToMany because it's unclear and to fully support both variants the current implementation isn't enough.
Hope that was somehow understandable.
In your case though you'll probably need to clean up unused tags yourself.
Related
I have a FileUpload entity that is child of other entities using the following association:
/**
* #ORM\ManyToOne(targetEntity="FileUpload", cascade={"persist", "remove"} )
* #ORM\JoinColumn(name="image_id", referencedColumnName="id")
*/
protected $image;
The FileUpload entity contains various information about an uploaded file as well as a boolean field to mark it for deletion (handled by a checkbox on the form). I am trying to find a good way of managing this deletion process without having to duplicate the code in every entity that has a FileUpload entity.
I tried creating a service tagged with doctrine.event_listener to remove the FileUpload in postUpdate(), however since there is still an association with the parent of the FileUpload this failed. Does anyone know a way of clearing any associations with the FileUpload when it is deleted? Or any other method of handling this process?
Sorry for the late answer. Now I understand your problem.
In the relation you described on the side has to be the owner of the relation. If let's say A is in relation with B and let's say that A is the owner of the relation, that implies that A has control over all the aspects of the relation so B can't be deleted without A say so.
Think about the foreign key relation of a database. The database won't let you delete the line as long as it is part of a relationship and is not the owner of the relation(this being your current problem).
If you get into a place where you need to delete a FileUpload without knowing the Comment that has a relation to it you may have an architectural problem in your application/database design. If you know the Comment that has a relation to the FileUpload at the point that you want to remove the file then orphanRemoval is what you need. The way you remove it is not by asking the Manager to remove it (cause it can't do it without the approval of the owner of the relation, as explained in the example above). Instead, you ask the owner of the relation to removing it something like this
//for OneToOne relation
$comment->setFileUpload(null);
//for OneToMany relation
$comment->getFileUpload()->removeElement($fileUpload);
After the above statement call flush and it should work. Also for OneToMany make sure that you initialize
$this->fileUpload = new ArrayCollection();
in the Comment entity constructor.
NOTE1: As mentioned before careful with orphan removal cause it doesn't work as you will expect in relation to refresh function of the manager. After an object was market in doctrine unit of work as an orphan, it will get removed even if you called refresh on it, or it's parent. Found a way around this (using the doctrine onFlush event) but is better to not need this and try to avoid the situation.
NOTE2: orphanRemoval has the effect of hard delete in the database. If as some point you need to do add this code to a doctrine subscriber or onFlush listener
public function onFlush(OnFlushEventArgs $args)
{
foreach ($args->getEntityManager()->getUnitOfWork()->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof SoftRemovableInterface) {
$args->getEntityManager()->remove($entity);
$args->getEntityManager()->persist($entity);
$entity->remove();
}
$args->getEntityManager()->getUnitOfWork()->computeChangeSet($args->getEntityManager()->getClassMetadata(get_class($entity)), $entity);
}
}
Where the remove+persist calls are there to take the entity out of the orphan removal list in unit of work (part of the fix for refreshing entities in NOTE1, this is the only place and only way i found that you can stop the removal of an orphan after it was marked as such by doctrine), and $entity->remove(); is the method of the SoftRemovableInterface that handles the soft delete, something like
class Comment implements SoftRemovableInterface
{
/........../
function remove()
{
$this->deleted = true;
}
}
Hope this brings some light to your issue. Happy coding.
Alexandru Cosoi
Alright. This question is kind of hard to describe. But here goes. I'll post some images first, just incase someone gets what I'm doing from this image;
A Block is an element that can be used to fill a webpage or blogpost. This can be images, text or forms. These Blocks are ContentBlocks. Block has a DiscriminatorColumn and DiscriminatorMap properties that are used to join the right Block table and create the underlying Block element. (i.e. an ImageContentBlock)
On the other hand we have Forms. Forms consist of FormBlocks. These are certain common Form elements. (TextField, PhoneField etc).
I want to be able to relate to the Content- or FormBlocks from either Page, Post or Form.
How can I achieve this in Doctrine?
I could add an entityType and entityId field to the Block class. But that would remove the object orientated style of programming. I would rather refer to the owning ContentEntity. But then again. I need to join or relate to the Blocks.
Not every ContentEntity has Blocks. So I cannot add this as an property of ContentEntity.
Now. I could off course use a ManyToMany relationship and go with a JoinTable. I guess that would always work. But I would have to join twice.
I think your problem isn't primarily about the data relations but about the fact that you want to avoid duplicate code. This results in your “Entity” being at the top of your hierarchy, only because it has a few common properties that each entity should have. (By the way, naming an entity “Entity” is a bit confusing.)
Maybe what you're looking for are Traits. So, instead of providing id and active through an entity, it could as well be a trait:
trait CmsEntity
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="boolean")
*/
protected $active;
// add getters/setters and other code as you like
}
Now you can attach this trait to all the entities which should have the given properties.
/**
* #ORM\Entity
*/
class Page
{
use CmsEntity; // import the trait
/**
* #ORM\Column(type="text")
*/
private $header;
// etc.
}
This will make you free from the requirement of deriving all your entities from one master “Entity” which just carries some common properties.
And now you can create a direct relation between between “ContentEntity” and “Block” (1:n I'd guess), which is more logical.
There's also a nice article elaborating on using Doctrine with traits for further reading.
I have these entities on my code.
class Review extends BaseEntity {
/** #ORM\OneToOne(targetEntity="Action", mappedBy="review") */
protected $action;
}
class Action extends BaseEntity {
/** #ORM\OneToOne(targetEntity="Review", inversedBy="action") */
protected $review;
}
As you can see it's a OneToOne relationship between Action and Review. My problem is I use soft-delete for my entities as well, so when I delete an entity is not actually removed only a deletion date is set. Later in audit trail I need to show deleted reviews and I also need information from the parent action of course. My question is, do I need to make this relationship OneToMany? or is there a better approach?
To be honest i'm not very found of the soft-delete behaviour. What you need to be aware is that soft-deleting an entity is a strong compromise in a relational database.
You may want to consider in this particular instance an event sourcing approach. I would recommend to store the information about the deletion and the (soft)deleted entity in a document based DB.
Any other approach (like OneToMany) is still fine but keep in mind that the risk here is degrading your relation by introducing a incoherent relationship.
That being said I'm fully aware that real life it's quite different than theory :) good luck.
Regards.
I have the following problem with clearing entities in doctrine:
I have two entities connected together, one of which is the main-entity so to say and one of which is the sub-entity that belongs to the main-entity. Then when I use the according repository to clear, it only clears the main-entity but leaves the sub-entity.Flushing the entities is actually working fine, because the entities are connected via cascade, so when I flush the main entity, the sub-entity gets flushed also. But this cascading does not seem to work with the clear.
Is there a way to also clear all the sub-entites together with the main entities without creating an extra (and actually not needed for other things) repository for the sub-entities?
Thank you in advance.
EDIT: Ok since I've seemingly been too unspecific, my goal is to clear the entities in doctrine, not delete them in the database. The problem is, that I have a lot of entities to process and doctrine doesn't clear up all entity references in the memory. So is there a way to cascade that, or do I need the repositories for that?
LAST EDIT: Problem has been fixed by doctrine. See accepted answer!
As of the last commit, the issue is fixed. Now cascade clearing works like a charm for me.
Thanks a lot to the developers of doctrine!
Additional info, see here:
https://github.com/doctrine/doctrine2/pull/995#issuecomment-40562699
https://github.com/doctrine/doctrine2/commit/1cd0b26a40dc22b0d11b1860eb058ab9cdc29f36
You need to add cascade={"remove"} to your mapping information in the main entity for field that represent sub-entity.
You can use cascade={"remove"} for each main-entity and sub-entity; it will be do the removal for children on memory which may lead to performance overhead.
If you want to rely on DB for removal which is faster and reliable you can configure each column on delete event onDelete="CASCADE"
(however you did not provide any code but as example see below)
/**
* #ORM\OneToMany(targetEntity="SubEntity", mappedBy="mainEntity")
*/
protected $subEntiy;
and in the other entity you have
/**
* #ORM\ManyToOne(targetEntity="MainEntity", inversedBy="subEntity")
* #ORM\JoinColumn(name="entity_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $mainEntity;
I am in the process of upgrading from Doctrine 1.1.4 to Doctrine 2.0.6 in my Zend application.
Currently, I am working on mapping the associations between entities. In Doctrine 2's Documentation it says 'relationships maybe bidirectional or unidirectional. I am confused as to what these terms mean within the given context.
How do I determine if a relationship is unidirectional or bidirectional?
Appreciate the help.
A relationship is bidirectional if both entities contain a reference to the other.
If you omit one of those references, it's unidirectional.
Consider a typical "posts" and "tags" schema. Typically, you'd implement a bidirectional association:
<?php
class Post {
// ...
/**
* #ManyToMany(targetEntity="Tag",inversedBy="posts")
*/
protected $tags;
// ...
}
class Tag {
// ...
/**
* #ManyToMany(targetEntity="Post",mappedBy="tags")
*/
protected $posts
// ...
}
Now, imagine you decided you never (or rarely) needed to answer questions like "Which posts have Tag 'foo'?". You could omit the $posts association in your Tag entity, converting it to a unidirectional association, and take some load off of the ORM.
You could still answer that kind of question, but you'd have to write code to do it.
In fact, it's probably a good way to go in Posts/Tags scenario, as you wouldn't typically be adding/removing Posts from Tags. Typically, you'd add/remove tags from posts only. You'd only ever go from Tags to Posts when looking for "all posts with tag 'x'", which could be trivially implemented in a service class of some sort.
Same as timdev`s answer,
Unidirectional & BiDirectional is just ORM Concepts, these have nothing to do with database,
Suppose you have a OneToMany relation -
user has blogs
So you can add this to your User Entity as OneToMany Property
but obviously there exisits ManyToOne Relation
Blogs Has User
so it is optional for you to create a ManyToOne relation in your Blog Entity, If you want to access user from blog entity then add this property if you dont want then dont add,its not necessary. in both Cases (you are adding bidirectional reference or not) ORM will maintain same database structure (blog table will have user_id column) .