Doctrine SoftDelete OneToOne Relationship - php

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.

Related

Which is the proper way to create Entity classes in PHP

According to doctrine documentation am reading, it says concerning using
Entity classes
that all of the fields should be protected or private (not public) and this is the quote.
When creating entity classes, all of the fields should be protected or
private (not public ), with getter and setter methods for each one
(except $id ). The use of mutators allows Doctrine to hook into calls
which manipulate the entities in ways that it could not if you just
directly set the values with entity#field = foo;
While the 6th edition of an advanced PHP book(One of the best selling books on PHP and other programming books out there are being written by this company) I just read says this
In most cases, private properties are strongly preferred over public
ones. However, in the case of entity classes, you should use public
properties. The sole purpose of an entity class is to make some data
available. It’s no good having a class representing an author if you
can’t even read the author’s name!
I understand that the pattern used by doctrine might slightly be different from the book approach but when you see statements like this, you get to wonder which is which. Which of the statement is wrong and which of the statement is right
The entire house should please enlighten me

"Real" orphan removal with Doctrine/MySQL

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.

Doctrine - Remove parent association when deleting child record

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

Optimistic lock in Doctrine2 doesn't work for many to many relation

I have entity User which has many to many relation to roles. I've tried to implement Optimistic lock, everything works fine, just when I changed roles, it doesn't change the version (User entity version), is this proper behaviour?
class User {
/**
* User's roles.
*
* #ORM\ManyToMany(targetEntity="Role")
*/
private $roles;
...
Doctrine 2's locking mechanisms don't take associations into account. They only protect against changes to the entities themselves. IMHO this is expected, because it has no way to know which associations to include and which to ignore. This isn't something you'd want to happen blindly on all associations.
Theoretically Doctrine 2 could achieve this by adding an option to the association mappings, but at this moment that simply isn't supported.
So you have 2 options:
Try to implement such a feature, and submit a PR :)
Implement your own optimistic locking mechanism that does take this specific association into account.
I didn't try, but I think this is proper bahaviour (because flush don't modify User entity) and there is no reason to lock User entity - it's not changed.

Relations in your model in a MVC application?

Now I have an model User which represents an user in the application. And I use an UserRepository with methods like getById($id) and getAll().
An user can post reviews. If I want to show the 5 or 10 or maybe 20 last reviews of an user it's most logical to ask the user I think. So I would have a method $user->getLastReviews(5).
But what's the best way to implement this? Give each user an instance of the ReviewRepository (with a method $reviewRepository->getByUser(User $user) for example)? Or are there better ways?
I think it's fine to have models contain and use instances of other models, so your way is fine, to have User model contain an instance of the Review model, and let that Review model handle the logic of getting said reviews.
Also, you could add a method to the UserRepository to handle it like so:
class UserRepository extends Model{
$review = new reviewRepository();
function getReviews($numOfReviews){
return $this->review->getReviews($user_id);
}
Another option would be to create a repository method where you passed in both variables. Something like $reviewRepository->getLastReviewsByUser(5, $user).
Usually this is a job for the ORM. Almost every framework uses one implementation (ie. Doctrine for PHP/Symfony or Hibernate for Java) but naturally you can implement your own ORM (ORM are often implemented using introspection).
Once you have an ORM library you define relations between Models in a "setup phase" (in your case you'll have "user has many reviews"). Then you'll use the ORM methods which knows how to deal with those ones (often relations are mutual ie. "review belongs to user"). The concept is that this setup phase will discharge you from dealing with issues like the one you pointed.
My suggestion is to use one of the already existing ORM implementations which already supplies facilities for getter and setter methods of related Models. In the other case, you have to write specialized getters and setters by yourself for every Model.

Categories