unitofwork.rst 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. Doctrine Internals explained
  2. ============================
  3. Object relational mapping is a complex topic and sufficiently understanding how Doctrine works internally helps you use its full power.
  4. How Doctrine keeps track of Objects
  5. -----------------------------------
  6. Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an
  7. object from the database, Doctrine will keep a reference to this object inside
  8. its UnitOfWork. The array holding all the entity references is two-levels deep
  9. and has the keys "root entity name" and "id". Since Doctrine allows composite
  10. keys the id is a sorted, serialized version of all the key columns.
  11. This allows Doctrine room for optimizations. If you call the EntityManager and
  12. ask for an entity with a specific ID twice, it will return the same instance:
  13. .. code-block:: php
  14. public function testIdentityMap()
  15. {
  16. $objectA = $this->entityManager->find('EntityName', 1);
  17. $objectB = $this->entityManager->find('EntityName', 1);
  18. $this->assertSame($objectA, $objectB)
  19. }
  20. Only one SELECT query will be fired against the database here. In the second
  21. ``EntityManager#find()`` call Doctrine will check the identity map first and
  22. doesn't need to make that database roundtrip.
  23. Even if you get a proxy object first then fetch the object by the same id you
  24. will still end up with the same reference:
  25. .. code-block:: php
  26. public function testIdentityMapReference()
  27. {
  28. $objectA = $this->entityManager->getReference('EntityName', 1);
  29. // check for proxyinterface
  30. $this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
  31. $objectB = $this->entityManager->find('EntityName', 1);
  32. $this->assertSame($objectA, $objectB)
  33. }
  34. The identity map being indexed by primary keys only allows shortcuts when you
  35. ask for objects by primary key. Assume you have the following ``persons``
  36. table:
  37. ::
  38. id | name
  39. -------------
  40. 1 | Benjamin
  41. 2 | Bud
  42. Take the following example where two
  43. consecutive calls are made against a repository to fetch an entity by a set of
  44. criteria:
  45. .. code-block:: php
  46. public function testIdentityMapRepositoryFindBy()
  47. {
  48. $repository = $this->entityManager->getRepository('Person');
  49. $objectA = $repository->findOneBy(array('name' => 'Benjamin'));
  50. $objectB = $repository->findOneBy(array('name' => 'Benjamin'));
  51. $this->assertSame($objectA, $objectB);
  52. }
  53. This query will still return the same references and `$objectA` and `$objectB`
  54. are indeed referencing the same object. However when checking your SQL logs you
  55. will realize that two queries have been executed against the database. Doctrine
  56. only knows objects by id, so a query for different criteria has to go to the
  57. database, even if it was executed just before.
  58. But instead of creating a second Person object Doctrine first gets the primary
  59. key from the row and check if it already has an object inside the UnitOfWork
  60. with that primary key. In our example it finds an object and decides to return
  61. this instead of creating a new one.
  62. The identity map has a second use-case. When you call ``EntityManager#flush``
  63. Doctrine will ask the identity map for all objects that are currently managed.
  64. This means you don't have to call ``EntityManager#persist`` over and over again
  65. to pass known objects to the EntityManager. This is a NO-OP for known entities,
  66. but leads to much code written that is confusing to other developers.
  67. The following code WILL update your database with the changes made to the
  68. ``Person`` object, even if you did not call ``EntityManager#persist``:
  69. .. code-block:: php
  70. <?php
  71. $user = $entityManager->find("Person", 1);
  72. $user->setName("Guilherme");
  73. $entityManager->flush();
  74. How Doctrine Detects Changes
  75. ----------------------------
  76. Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
  77. This means you map php objects into a relational database that don't
  78. necessarily know about the database at all. A natural question would now be,
  79. "how does Doctrine even detect objects have changed?".
  80. For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
  81. an object from the database Doctrine will keep a copy of all the properties and
  82. associations inside the UnitOfWork. Because variables in the PHP language are
  83. subject to "copy-on-write" the memory usage of a PHP request that only reads
  84. objects from the database is the same as if Doctrine did not keep this variable
  85. copy. Only if you start changing variables PHP will create new variables internally
  86. that consume new memory.
  87. Now whenever you call ``EntityManager#flush`` Doctrine will iterate over the
  88. Identity Map and for each object compares the original property and association
  89. values with the values that are currently set on the object. If changes are
  90. detected then the object is queued for a SQL UPDATE operation. Only the fields
  91. that actually changed are updated.
  92. This process has an obvious performance impact. The larger the size of the
  93. UnitOfWork is, the longer this computation takes. There are several ways to
  94. optimize the performance of the Flush Operation:
  95. - Mark entities as read only. These entities can only be inserted or removed,
  96. but are never updated. They are omitted in the changeset calculation.
  97. - Temporarily mark entities as read only. If you have a very large UnitOfWork
  98. but know that a large set of entities has not changed, just mark them as read
  99. only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``.
  100. - Flush only a single entity with ``$entityManager->flush($entity)``.
  101. - Use :doc:`Change Tracking Policies <change-tracking-policies>` to use more
  102. explicit strategies of notifying the UnitOfWork what objects/properties
  103. changed.
  104. Query Internals
  105. ---------------
  106. The different ORM Layers
  107. ------------------------
  108. Doctrine ships with a set of layers with different responsibilities. This
  109. section gives a short explanation of each layer.
  110. Hydration
  111. ~~~~~~~~~
  112. Responsible for creating a final result from a raw database statement and a
  113. result-set mapping object. The developer can choose which kind of result he
  114. wishes to be hydrated. Default result-types include:
  115. - SQL to Entities
  116. - SQL to structured Arrays
  117. - SQL to simple scalar result arrays
  118. - SQL to a single result variable
  119. Hydration to entities and arrays is one of most complex parts of Doctrine
  120. algorithm-wise. It can built results with for example:
  121. - Single table selects
  122. - Joins with n:1 or 1:n cardinality, grouping belonging to the same parent.
  123. - Mixed results of objects and scalar values
  124. - Hydration of results by a given scalar value as key.
  125. Persisters
  126. ~~~~~~~~~~
  127. tbr
  128. UnitOfWork
  129. ~~~~~~~~~~
  130. tbr
  131. ResultSetMapping
  132. ~~~~~~~~~~~~~~~~
  133. tbr
  134. DQL Parser
  135. ~~~~~~~~~~
  136. tbr
  137. SQLWalker
  138. ~~~~~~~~~
  139. tbr
  140. EntityManager
  141. ~~~~~~~~~~~~~
  142. tbr
  143. ClassMetadataFactory
  144. ~~~~~~~~~~~~~~~~~~~~
  145. tbr