I am working with two entities related by a 'one to many' relation but I'm having some problems.
Let say the entity Article has a property with a collection of Post's, and I configure the entities to use cascade. Something like that:
#ORM\OneToMany(targetEntity="Post", mappedBy="article", cascade={"remove", "persist"}, orphanRemoval=true)
When I try to save a instance of Article with his nested instances of Post's works fine but I don't want to save all the Post's anytime I change a property of the Article.
Seems like once you have the cascade relation defined you can only save using the cascade. Is not possible to enable and disable the cascade on demand and save a single entity without having to save all his childrens entities too?
Someone had this problem before and solved that somehow?
Thanks in advance!
This is expected behavior. Try to use events to update/save related entities
Related
I'm migrating the DBAL of a ZF3 application to Doctrine and want to go ahead step by step. Currently I'm using a hierarchy of Mapper objects. Each entity in the like FooEntity hierarchy has an according FooMapper. Saving of nested entities is performed by nested Mappers. Every Mappers saves its entity with Zend\Db\Sql\Insert or Zend\Db\Sql\Update and calls the proper Mappers for the sub-entities like BarMapper for BarEntity.
Now, before I start with Doctrine's convenience features like cascade={"persist"}, I want to keep the Mapper's hierarchy and just to perform the saving of the top level of the nested entity with persist(...) & flush().
But when I try it
public function save(AbstractDataObject $dataObject)
{
$newLogicalConnection = $this->logicalConnectionMapper->save($dataObject->getLogicalConnection());
$newUser = $this->userMapper->save($dataObject->getUser());
$dataObject->setLogicalConnection($this->entityManager->find(LogicalConnection::class, $newLogicalConnection->getId()));
$dataObject->setUser($this->entityManager->find(User::class, $newUser->getId()));
$this->entityManager->persist($dataObject);
$this->entityManager->flush();
return $dataObject;
}
I get an error
A new entity was found through the relationship 'MyNamespace\DataObject\AbstractEndpoint#externalServer' that was not configured to cascade persist operations for entity: MyNamespace\DataObject\ExternalServer#000000006098ccff0000000068c23676. 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 'MyNamespace\DataObject\ExternalServer#__toString()' to get a clue.
So, Doctrine seems to try saving the whole entity with its sub-entities, and this attempt fails on one of the lower levels. But why? I have not activated any cascade options and expect Doctrine to save only the top level.
Why does Doctrine try to save the whole entity and not only the top level? And how to get it saving only the top level of the given entity?
You get this error because you have a new entity (not persisted yet) in AbstractEndpoint->externalServer and this field is not annotated as cascade={"persist"}
In other words you have just created a new entity ExternalServer and did not persisted it and added it as a relation to AbstractEndpoint->externalServer entity which is not annotated as cascade={"persist"}
So Doctrine ends up having this new entity and does not know what to do with it. In order not to lost any data this exception is raised.
To fix this you can do two things:
Add $this->entityManager->persist($externalServer); right after you create ExternalServer entity
Annotate AbstractEndpoint->externalServer with cascade={"persist"}. Which you don't want to do because you want only top level entity to be saved to DB so you need to persist it manually or DO NOT add it is a relation.
And now answering your question:
But why? I have not activated any cascade options and expect Doctrine to save only the top level.
Somehow through relations in your object model Doctrine goes down to ExternalServer entity and finds it in unpersisted state. You can not save only top level of object hierarchy with link to unexisting record in relational database. If you do not want Doctrine to do it for you - you must handle this situations by yourself or remove not persisted entities from relations
I have 4 tables. Two of them are monomorphic and two of them are polymorphic.
Monomorphic:
Templates, Modules
Polymorphic:
Documents, Images
Now, templates and modules have both documents and images and each Template has many Modules and modules have foreign key that is set to cascade on deletion of templates.
Now, if I delete a Template the associated Modules will be deleted but the associated polymorphic relations of Module will stay in Database. I haven't tried anything because I am completely clueless.
Anything I could do to automatically delete associations of Module when Template is deleted? I think the deletion here in this is being handled by Database itself and Eloquent doesn't have anything to do with it.
Because it is a polymorphic relationship, the cascading delete cannot be handled by a foreign key.
If you still want the deletes to be handled at the database level, you will need to write some type of database trigger functionality.
There are also a couple options if you want to handle this at the application level.
One option is to update your code so that everywhere you delete your Template, you also make sure to delete all of it's polymorphic relations.
Another option would be to create an event handler for the deleting event. The deleting event is fired by Eloquent whenever you delete an Eloquent model. Inside this deleting event, you can access the polymorphic relationship and delete them, as well.
If the deleting event method sounds okay, there is a package that implements all of this for you, and makes it very easy to setup for your models: shiftonelabs/laravel-cascade-deletes. Full disclosure: I wrote the package.
With this package, you just add the CascadesDeletes trait to your Template model, and then add a $cascadeDeletes property that contains an array of all the relationships to delete when a Template instance is deleted.
It's not fully automatic, but I handle it very easy with the destroy() Eloquent method. https://laravel.com/docs/8.x/eloquent#deleting-an-existing-model-by-key In addition, when you will use it with Laravel Queues, it works like a charm...
Example:
$template = Template::find(1);
Module::destroy($template->modules);
$template->delete();
You can actually use delete method on both types of relationship:
$template = Template::find(1);
$template->modules()->delete();
$template->images()->delete();
$template->delete();
Key note here is that $template->modules returns a collection instance while $template->modules() return an eloquent relationship where you can chain delete() method.
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
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) .