Flushing one entity flushes also changes done in not related PersistentCollection #5789

Closed
opened 2026-01-22 15:17:56 +01:00 by admin · 9 comments
Owner

Originally created by @simara-svatopluk on GitHub (Nov 29, 2017).

Originally assigned to: @Ocramius on GitHub.

Scenario:

  • Let's assume a couple of entities with collections in a database
  • Select two and clean their collections
  • Flush only one entity
  • The other entity collection is also flushed - bug

The problem is that PersistentCollection manipulate directly UnitOfWork and changes are flushed even if some another entity is flushed.

Test: 200f659d68
Important is line 40; fails on 49

Originally created by @simara-svatopluk on GitHub (Nov 29, 2017). Originally assigned to: @Ocramius on GitHub. Scenario: * Let's assume a couple of entities with collections in a database * Select two and clean their collections * Flush only one entity * The other entity collection is also flushed - bug The problem is that PersistentCollection manipulate directly UnitOfWork and changes are flushed even if some another entity is flushed. Test: https://github.com/simara-svatopluk/doctrine2/commit/200f659d68ad9b8bfb6eee7e048fffe8e909e42c Important is line 40; fails on 49
admin added the BugInvalidQuestion labels 2026-01-22 15:17:56 +01:00
admin closed this issue 2026-01-22 15:18:01 +01:00
Author
Owner

@Ocramius commented on GitHub (Nov 29, 2017):

flush($foo) is a performance optimisation, not a restriction. Calling flush($foo) is:

  • deprecated
  • not endorseable, as it just means "check for changes in $foo, whilst still saving changes computed so far
@Ocramius commented on GitHub (Nov 29, 2017): `flush($foo)` is a performance optimisation, not a restriction. Calling `flush($foo)` is: * deprecated * not endorseable, as it just means "check for changes in `$foo`, whilst still saving changes computed so far
Author
Owner

@simara-svatopluk commented on GitHub (Nov 29, 2017):

Thank for an extremely fast response!

Am I able to determine which changes will be persisted and which not? Because detach is (or will be) deprecated too.

The whole use-case is that I'm implementing Repositories from DDD, and in one repository I assume that only one aggregate will be written to database/storage. I do not assume that the whole system state will be persisted.

@simara-svatopluk commented on GitHub (Nov 29, 2017): Thank for an extremely fast response! Am I able to determine which changes will be persisted and which not? Because detach is (or will be) deprecated too. The whole use-case is that I'm implementing Repositories from DDD, and in one repository I assume that only one aggregate will be written to database/storage. I do not assume that the whole system state will be persisted.
Author
Owner

@Ocramius commented on GitHub (Nov 29, 2017):

Am I able to determine which changes will be persisted and which not? Because detach is (or will be) deprecated too.

It is possible to check that during the onFlush event, but I suggest avoiding going down that road.

I do not assume that the whole system state will be persisted.

Then you will need an EntityManager instance per repository ;-)

@Ocramius commented on GitHub (Nov 29, 2017): > Am I able to determine which changes will be persisted and which not? Because detach is (or will be) deprecated too. It is possible to check that during the `onFlush` event, but I suggest avoiding going down that road. > I do not assume that the whole system state will be persisted. Then you will need an `EntityManager` instance per repository ;-)
Author
Owner

@lcobucci commented on GitHub (Nov 29, 2017):

The whole use-case is that I'm implementing Repositories from DDD, and in one repository I assume that only one aggregate will be written to database/storage. I do not assume that the whole system state will be persisted.

Just to make sure... you're not flushing in the repository, right?

@lcobucci commented on GitHub (Nov 29, 2017): > The whole use-case is that I'm implementing Repositories from DDD, and in one repository I assume that only one aggregate will be written to database/storage. I do not assume that the whole system state will be persisted. Just to make sure... you're not flushing in the repository, right?
Author
Owner

@simara-svatopluk commented on GitHub (Nov 29, 2017):

@lcobucci yep, I do. Do You suggest a better place?

But repository... it's not Doctrine Repository, it is implementation of an interface

@simara-svatopluk commented on GitHub (Nov 29, 2017): @lcobucci yep, I do. Do You suggest a better place? But repository... it's not Doctrine Repository, it is implementation of an interface
Author
Owner

@simara-svatopluk commented on GitHub (Nov 29, 2017):

I do not assume that the whole system state will be persisted.
Then you will need an EntityManager instance per repository ;-)

This wouldn't help either because all instances of a class will be flushed.

@simara-svatopluk commented on GitHub (Nov 29, 2017): > > I do not assume that the whole system state will be persisted. > Then you will need an EntityManager instance per repository ;-) This wouldn't help either because all instances of a class will be flushed.
Author
Owner

@lcobucci commented on GitHub (Nov 29, 2017):

@lcobucci yep, I do. Do You suggest a better place?

@simara-svatopluk IMO repositories should only call persist() or remove() (for writing to the DB, sure), the flushing should be controlled by an upper layer (the one that handles the transactional part of your software). For example, I use tactician to handle commands/queries and I have a middleware which calls EntityManager#flush() for me after everything is done.

But regardless of the architecture of your software, the idea of EntityManager#flush() is to commit the unit of work - which means I'm done manipulating my objects for a certain operation, please send everything you got to the database.

And finally, beware: if you're doing multiple flushes for a single operation (a command in my example) and you're not controlling DB transactions explicitly you'll definitely have partial rollbacks when things go wrong (which is not really nice for your DB consistency).

@lcobucci commented on GitHub (Nov 29, 2017): > @lcobucci yep, I do. Do You suggest a better place? @simara-svatopluk IMO repositories should only call `persist()` or `remove()` (for writing to the DB, sure), the flushing should be controlled by an upper layer (the one that handles the transactional part of your software). For example, I use tactician to handle commands/queries and I have a [middleware](https://github.com/thephpleague/tactician-doctrine/blob/master/src/ORM/TransactionMiddleware.php) which calls `EntityManager#flush()` for me after everything is done. But regardless of the architecture of your software, the idea of `EntityManager#flush()` is to commit the unit of work - which means I'm done manipulating my objects for a certain operation, please send everything you got to the database. And finally, beware: if you're doing multiple flushes for a single operation (a command in my example) and you're **not** controlling DB transactions explicitly you'll definitely have partial rollbacks when things go wrong (which is not really nice for your DB consistency).
Author
Owner

@simara-svatopluk commented on GitHub (Nov 29, 2017):

@lcobucci You made me think, thanks! I'll start from the end...

Transactions - You are right, sometimes inter-aggregates consistency is required.

I understand that EntityManager#flush() means - And now save everything that is handled by UnitOfWork to the database.

And now the most difficult - what is a repository?

For me, a repository is an abstract drawer (interface). I can look for a given entity by identity and entity can be there or not function get($id). I can add a new entity to this abstract drawer function add($entity]. And finally when I find a given entity, I have it in my hands, I can modify this entity and then put it back to the drawer function save($entity)

Since repository is an interface, it has to be implemented somehow, I've already worked with Doctrine, Elasticsearch, Cookie, Redis implementation. So an interface is pretty useful because I don't know the implementation in a domain layer. The problem is with transactions - not all technologies support transactions, but still, we can have a middleware/transaction manager that handle this.

And now all problems together - if I have an entity in my hands, I modify it, but I do not put it back to the drawer, shall it be in fact saved to any database?
Why? When a use-case involves more aggregates, usually I want to save only one. But other aggregates still support changing operations, I can call them accidentally (because I have no idea they change state), and when I call flush(), I have no idea and no control on what was persisted to the database and what was not.

What is a repository for You?

@simara-svatopluk commented on GitHub (Nov 29, 2017): @lcobucci You made me think, thanks! I'll start from the end... Transactions - You are right, sometimes inter-aggregates consistency is required. I understand that `EntityManager#flush()` means - And now save everything that is handled by UnitOfWork to the database. And now the most difficult - what is a repository? For me, a repository is an abstract drawer (interface). I can look for a given entity by identity and entity can be there or not `function get($id)`. I can add a new entity to this abstract drawer `function add($entity]`. And finally when I find a given entity, I have it in my hands, I can modify this entity and then put it back to the drawer `function save($entity)` Since repository is an interface, it has to be implemented somehow, I've already worked with Doctrine, Elasticsearch, Cookie, Redis implementation. So an interface is pretty useful because I don't know the implementation in a domain layer. The problem is with transactions - not all technologies support transactions, but still, we can have a middleware/transaction manager that handle this. And now all problems together - if I have an entity in my hands, I modify it, but I do not put it back to the drawer, shall it be in fact saved to any database? Why? When a use-case involves more aggregates, usually I want to save only one. But other aggregates still support changing operations, I can call them accidentally (because I have no idea they change state), and when I call `flush()`, I have no idea and no control on what was persisted to the database and what was not. What is a repository for You?
Author
Owner

@simara-svatopluk commented on GitHub (Feb 5, 2018):

@lcobucci Thanks for Your point of view. I read more about DDD repositories and now I can only agree that their responsibility is not save. Repositories supplement in-memory collections that uses references to objects, and there is no save use case.

@simara-svatopluk commented on GitHub (Feb 5, 2018): @lcobucci Thanks for Your point of view. I read more about DDD repositories and now I can only agree that their responsibility is not `save`. Repositories supplement in-memory collections that uses references to objects, and there is no save use case.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5789