Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doctrine isDeletable method in entity

I have a Entity in my database (say Member) which has many relationships with other tables (6 relationships to be exact). Some of them I don't want mapped with the ORM (I mean linked to this Entity) because they may have many records (like MemberAccessLogs for example) and some other load many other entities.

Now I want this Member Entity to have an isDeletable method so I can disable exclude button in administration page.

If I where to do this the traditional way, I would have to declare the associations with all the other tables in the entity class, including MemberAccessLogs and I would put the method in it so I could test if these associations are empty.

But AFAIU, I would have to make a fetch (or at least a count) to the association's tables in order check for empty.

Another way would be to fetch the Members I want shown and then make a separate query to check for empty with a low cost exists(select * from table limit 1) in these sub-tables and then populate the isDeletable method in Member programmatically before pass it to Twig.

But I found this solution cumbersome. Anyone has a better way to do this ?

Just for the record: Some people may think this is "premature optimization". I maintain (contrary to some), that you should think ahead when you are programming and don't this this is bad. But I really think this isn't the place to discuss it. Please let's focus on the question asked ok ? :)

Edit

To easily prove that limit 1 is increadibly faster than count, I did a small test in a table in my database that has more than 20 million lines. Here are the results:

select count(*) from loga [20 million+ table]
20678473
1 row(s) fetched - 27023ms

select exists(select null from loga limit 1) 
true
1 row(s) fetched - 2ms

I guess 13511,5 times faster is conclusive enough. :D

like image 200
Nelson Teixeira Avatar asked Aug 31 '15 18:08

Nelson Teixeira


1 Answers

Extra lazy

You could look into extra-lazy associations.

Basically you map all associations as you normally would, and add fetch="EXTRA_LAZY":

/**
 * @Entity
 */
class CmsGroup
{
    /**
     * @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
     */
    public $users;
}

Now Doctrine will not load the complete collection into memory the first time it's accessed, but performs specialized queries to load the parts you actually need at that moment.

So $users->count() (or count($users)) on the collection would trigger a simple count-query in stead of loading the complete collection into memory.

PostLoad

You could use an postLoad event to determine if such an entity is deletable. This postLoad event is called after an entity is constructed by the EntityManager, so when the entity is loaded.

Add an unmapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not.

Create an entity listener that listens to the postLoad event. The listener can have the EntityManager, DBAL Connection, or anything else injected. With that dependency you could perform whatever query you want and use the result to set $isDeletable.

The result is a single additional query when the entity is loaded, after which the entity "knows" whether it's deletable or not.

An example of using the postLoad event can found in a Cookbook entry on the Strategy Pattern

Do note that when the conditions that determine whether it's deletable or not change, the value of $isDeletable could become incorrect. To resolve this issue, you could keep track of those conditions:

Keep track

Add a mapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not. It would probably start with true.

When something is added to an association which would mean that the entity is no longer deletable, set $isDeletable to false.

When something is removed from an association that which would mean that the entity is deletable again, set $isDeletable to true.

In other words: with every change you keep track of whether the entity is deletable or not.

This way you won't need any additional queries at all.

There's a Cookbook entry on aggregate fields that explains this concept very well.

like image 85
Jasper N. Brouwer Avatar answered Sep 21 '22 08:09

Jasper N. Brouwer