skip to Main Content

In Doctrine ORM there is a separation between persist() and flush() operations. Looks like whenever you want to insert/update your entity, you have to call two methods one by one. And most developers do it this way. But I wonder if it was intended to be used this way.

Most of the time I see repositories like this:

class EntityRepo extends ServiceEntityRepository
{
    public function save(Entity $entity): void
    {
        $this->getEntityManager()->persist($entity);
        $this->getEntityManager()->flush();
    }
)

And this approach has a big flaw, even if leave verbosity aside – call to flush() will sync ALL the not-synced entities with database, not only the one we are saving now.
I would suggest to call

$this->getEntityManager()->flush($entity);

to restrict sync to only one entity, but this approach was deprecated by Doctrine.

In my understanding, flush() was intended to be called only once in "business transaction", it’s responsibility is to sync all the in-memory changes with database. And this adheres well to the "functional" idea of making domain core "pure" of any side effects: we just move all side effects outside the domain code. Am I wrong? What drawbacks has this approach?

Nor doctrine best practices, nor Ocramius does say anything about it.

What is the best practice of dealing with flush()?

I’ve tried to call flush() each time I call persist() and got undesired side-effects of saving/updating unrelated entities.
Also I tried to call flush() once in the end of "business transaction", for example in Controller, or in Middleware, but looks like this approach is unpopular and I am afraid that I misunderstood something.

2

Answers


  1. At first, you need to understand Doctrine use "Unit of work" pattern and "identity map" pattern.
    https://martinfowler.com/eaaCatalog/unitOfWork.html
    https://martinfowler.com/eaaCatalog/identityMap.html

    A problem exists with "native" sql connection, you can use many times and update many times the same entity, in différent moment, and it could result to different issues. So doctrine uses many different functions. :°

    Doctrine uses flush to be sure that all data is manage one time, properly update, lose no data, do not create too long transactions…

    So yes you are right, it is to prevent sides effects.

    To do this, doctrine need to know all entity, if there exist in db ect…
    This is why you persist a new entity. But you can also merge and unmerge entity (be really careful with this, because it can result of data lost, or unsynchronised data…) remove ect… entity load by entityManage is auto managed, so there is no need to merge theme for example 🙂

    For advantages :

    One of the biggest problème in transactions is that you can not use not commit object, so if you have an invoice, you cannot make payement before insert invoice in table. So you can not do this with secure data, and you can no avoid change if your program throw an error. Doctrine and flush fix this !

    In complexe data change or processing, it will help you to reduce sql request (less resquest, have better performance…)

    Any other less important but usefull like auto insert all element (no need to do request), "virtual collection" of element in many to many join ect…

    The drawback

    Need a lot of différent elements can easy results in conflicts when we use it incorrectly

    At last,
    flush as no good practices, use it anytime you need it. The good practices need to be used on other methods (persist, merge, unmerge, remove…) because they could create conflict (use other entityManager entity for exemple) hard to solve

    I hope i answr to your question 🙂

    Login or Signup to reply.
  2. You are correct that to call flush() on every instance of persist() would be considered an anti-pattern[1], however you need to think more broadly in the terms of Unit of Work:

    Single Persist to Flush situations:
    Sometimes this work unit is simply a single persist statement — such as creating a user, and then you’re done with that work, and on to something completely different. In those cases, yes, you’ll call flush() immediately after persist.

    Multiple Persists to Flush situations:
    Waiting to call flush() comes into play when we’re talking about a unit of work containing multiple tasks. For example, I might create a new company which results in new objects and created database updates for the company table, contact table, and perhaps a few other, all related to the "work" of adding a new company to the CRM. When persisted those objects become available (in memory so to speak) but have not yet been committed to the database. When you flush() it will now persist everything to the database (or in the terms of doctrine, they become synced with the database).

    Minimum Flush Interval:
    Doctrine says you should have 0-2 flush() for a HTTP request — meaning that you shouldn’t be flushing multiple times per HTTP request, because, conceptually, a single HTTP request consists of a single unit of work. And it should succeed or fail as a "unit".

    It can be dangerous to decuple your flush from a single HTTP request because that means that you have persisted data, that is currently available in the ORM, but it can get lost if it is never flushed at a later point.

    Overlapping Work:
    While I cannot think of a specific situation it seems like you might be implying that you’ve got more than one bit of overlapping work being performed, so you only want to flush certain work but not others. That would present a problem for the approach of a global flush. But it can be dangerous to have unflushed data at the end of a specific HTTP request (or perhaps even outside of a function/class). I would imagine that what you’re describing would be the bigger anti-pattern, of leaving unflished work on the table, while beginning a new task.

    [1] Official Doctrine Docs under Persisting Document – https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/working-with-objects.html#persisting-documents

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search