{"action":"created","issue":{"url":"https://api.github.com/repos/doctrine/doctrine2/issues/5550","labels_url":"https://api.github.com/repos/doctrine/doctrine2/issues/5550/labels{/name}","comments_url":"https://api.github.com/repos/doctrine/doctrine2/issues/5550/comments","events_url":"https://api.github.com/repos/doctrine/doctrine2/issues/5550/events","html_url":"https://github.com/doctrine/doctrine2/issues/5550","id":121790640,"number":5550,"title":"TableDataGateway interface for fetching/saving entities without UnitOfWork interactions","user":{"login":"Ocramius","id":154256,"avatar_url":"https://avatars.githubusercontent.com/u/154256?v=3","gravatar_id":"","url":"https://api.github.com/users/Ocramius","html_url":"https://github.com/Ocramius","followers_url":"https://api.github.com/users/Ocramius/followers","following_url":"https://api.github.com/users/Ocramius/following{/other_user}","gists_url":"https://api.github.com/users/Ocramius/gists{/gist_id}","starred_url":"https://api.github.com/users/Ocramius/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Ocramius/subscriptions","organizations_url":"https://api.github.com/users/Ocramius/orgs","repos_url":"https://api.github.com/users/Ocramius/repos","events_url":"https://api.github.com/users/Ocramius/events{/privacy}","received_events_url":"https://api.github.com/users/Ocramius/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/doctrine/doctrine2/labels/New%20Feature","name":"New Feature","color":"bfdadc"}],"state":"open","locked":false,"assignee":{"login":"Ocramius","id":154256,"avatar_url":"https://avatars.githubusercontent.com/u/154256?v=3","gravatar_id":"","url":"https://api.github.com/users/Ocramius","html_url":"https://github.com/Ocramius","followers_url":"https://api.github.com/users/Ocramius/followers","following_url":"https://api.github.com/users/Ocramius/following{/other_user}","gists_url":"https://api.github.com/users/Ocramius/gists{/gist_id}","starred_url":"https://api.github.com/users/Ocramius/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Ocramius/subscriptions","organizations_url":"https://api.github.com/users/Ocramius/orgs","repos_url":"https://api.github.com/users/Ocramius/repos","events_url":"https://api.github.com/users/Ocramius/events{/privacy}","received_events_url":"https://api.github.com/users/Ocramius/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/doctrine/doctrine2/milestones/1","html_url":"https://github.com/doctrine/doctrine2/milestones/3.0","labels_url":"https://api.github.com/repos/doctrine/doctrine2/milestones/1/labels","id":1446272,"number":1,"title":"3.0","description":null,"creator":{"login":"beberlei","id":26936,"avatar_url":"https://avatars.githubusercontent.com/u/26936?v=3","gravatar_id":"","url":"https://api.github.com/users/beberlei","html_url":"https://github.com/beberlei","followers_url":"https://api.github.com/users/beberlei/followers","following_url":"https://api.github.com/users/beberlei/following{/other_user}","gists_url":"https://api.github.com/users/beberlei/gists{/gist_id}","starred_url":"https://api.github.com/users/beberlei/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/beberlei/subscriptions","organizations_url":"https://api.github.com/users/beberlei/orgs","repos_url":"https://api.github.com/users/beberlei/repos","events_url":"https://api.github.com/users/beberlei/events{/privacy}","received_events_url":"https://api.github.com/users/beberlei/received_events","type":"User","site_admin":false},"open_issues":21,"closed_issues":1,"state":"open","created_at":"2015-12-06T13:20:21Z","updated_at":"2015-12-11T21:02:59Z","due_on":null,"closed_at":null},"comments":17,"created_at":"2015-12-11T21:00:26Z","updated_at":"2015-12-12T12:33:28Z","closed_at":null,"body":"While I was at SymfonyCon this year, I discussed something interesting with @lisachenko: providing a `TableDataGateway`-alike interface for the ORM.\r\n\r\n### The current doctrine2 approach\r\n\r\n```php\r\n$entity = $someRepository->find(123);\r\n$entity->doSomething();\r\n$entityManager->flush();\r\n```\r\n\r\nWhile this approach is very practical, it also exposes some serious memory/transactional/scope limitations, as the `$entityManager` leaks through different service layers.\r\n\r\n### The proposed `TableDataGateway` approach\r\n\r\n```php\r\n$entity = $someRepository->find(123);\r\n$entity->doSomething();\r\n$someRepository->save($entity);\r\n```\r\n\r\nThe difference here is that the repository does not push the entity into any `UnitOfWork`: there are no `flush()` semantics, and calling `$someRepository->save($entity);` triggers an actual SQL query.\r\n\r\n### Compatibility\r\n\r\nFrom what I can see, everything currently existing in the ORM could work exactly as it is working right now: just the hydration process would be simplified.\r\n\r\n - all entities are always in detached state\r\n - `DQL` just produces new objects, rather than searching in the `UnitOfWork` for existing instances\r\n - Proxies just lazy-load as before\r\n - persisters just work as before\r\n - everything is always treated as \"dirty\": saving a hydrated aggregate will cause `INSERT`/`UPDATE` queries for all the already initialized sub-graph (we may examine this case further - maybe one unit of work per sub-graph?)\r\n - mapping stays as-is\r\n - collections stay as-is\r\n\r\n\r\n### Invariant changes\r\n\r\n - `$someRepository->find(123) !== $someRepository->find(123);`\r\n - race conditions between loading/saving objects must be fixed by using locking\r\n\r\n### Advantages\r\n\r\nThis sort of API has huge advantages when dealing with operations in:\r\n\r\n - batch processing (no more memory leaks there)\r\n - long running processes (memleaks gone, can reuse the same connection over multiple operations, without side-effects)\r\n - better handling of aggregates - different aggregates have different references and are incompatible\r\n - no more need to explicitly `persist()` an object before saving it\r\n\r\n### Cleaning up persisters/hydrators\r\n\r\nIn addition to all the above, this gives us the opportunity of examining scopes where the UnitOfWork leaked into, and to check whether we can get it out of there, and at what cost.\r\n"},"comment":{"url":"https://api.github.com/repos/doctrine/doctrine2/issues/comments/164145945","html_url":"https://github.com/doctrine/doctrine2/issues/5550#issuecomment-164145945","issue_url":"https://api.github.com/repos/doctrine/doctrine2/issues/5550","id":164145945,"user":{"login":"Akii","id":1204017,"avatar_url":"https://avatars.githubusercontent.com/u/1204017?v=3","gravatar_id":"","url":"https://api.github.com/users/Akii","html_url":"https://github.com/Akii","followers_url":"https://api.github.com/users/Akii/followers","following_url":"https://api.github.com/users/Akii/following{/other_user}","gists_url":"https://api.github.com/users/Akii/gists{/gist_id}","starred_url":"https://api.github.com/users/Akii/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Akii/subscriptions","organizations_url":"https://api.github.com/users/Akii/orgs","repos_url":"https://api.github.com/users/Akii/repos","events_url":"https://api.github.com/users/Akii/events{/privacy}","received_events_url":"https://api.github.com/users/Akii/received_events","type":"User","site_admin":false},"created_at":"2015-12-12T12:33:28Z","updated_at":"2015-12-12T12:33:28Z","body":"@DHager Good point. However @xtreamwayz should be right as it says \"everything is treated dirty\". So cascade persist would simply overwrite left engine when persisting B.\r\n\r\n@Jean85 My gut feeling tells me that this might not be the case due to proxies (lazy loading etc.).\r\n\r\n@Ocramius thanks for pointing out the advantages, makes really much sense in certain areas. Migrations could potentially also benefit from it."}}