PageRenderTime 27ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Doctrine/KeyValueStore/UnitOfWork.php

http://github.com/doctrine/KeyValueStore
PHP | 295 lines | 205 code | 50 blank | 40 comment | 23 complexity | 44a5a18e028ea4e55d233761a9ace537 MD5 | raw file
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the MIT license. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\KeyValueStore;
  20. use Doctrine\KeyValueStore\Id\IdHandlingStrategy;
  21. use Doctrine\KeyValueStore\Mapping\ClassMetadataFactory;
  22. use Doctrine\KeyValueStore\Storage\Storage;
  23. /**
  24. * UnitOfWork to handle all KeyValueStore entities based on the configured
  25. * storage mechanism.
  26. *
  27. * @author Benjamin Eberlei <kontakt@beberlei.de>
  28. */
  29. class UnitOfWork
  30. {
  31. /**
  32. * @var ClassMetadataFactory
  33. */
  34. private $cmf;
  35. /**
  36. * @var Storage
  37. */
  38. private $storageDriver;
  39. /**
  40. * @var IdHandlingStrategy
  41. */
  42. private $idHandler;
  43. /**
  44. * Serialized versions of the identifiers.
  45. *
  46. * This is the after {@see IdConverterStrategy#serialize} is called on the
  47. * entity data.
  48. *
  49. * @var array
  50. */
  51. private $identifiers;
  52. private $originalData;
  53. private $scheduledInsertions = [];
  54. private $scheduledDeletions = [];
  55. private $identityMap = [];
  56. private $idConverter;
  57. public function __construct(ClassMetadataFactory $cmf, Storage $storageDriver, Configuration $config = null)
  58. {
  59. $this->cmf = $cmf;
  60. $this->storageDriver = $storageDriver;
  61. $this->idConverter = $config->getIdConverterStrategy();
  62. $this->idHandler = $storageDriver->supportsCompositePrimaryKeys() ?
  63. new Id\CompositeIdHandler() :
  64. new Id\SingleIdHandler();
  65. }
  66. public function getClassMetadata($className)
  67. {
  68. return $this->cmf->getMetadataFor($className);
  69. }
  70. private function tryGetById($className, $id)
  71. {
  72. $idHash = $this->idHandler->hash($id);
  73. if (isset($this->identityMap[$className][$idHash])) {
  74. return $this->identityMap[$className][$idHash];
  75. }
  76. return;
  77. }
  78. public function reconstititute($className, $key)
  79. {
  80. $class = $this->cmf->getMetadataFor($className);
  81. $id = $this->idHandler->normalizeId($class, $key);
  82. $data = $this->storageDriver->find($class->storageName, $id);
  83. if (! $data) {
  84. throw new NotFoundException();
  85. }
  86. return $this->createEntity($class, $id, $data);
  87. }
  88. public function createEntity($class, $id, $data)
  89. {
  90. if (isset($data['php_class'])) {
  91. if ($data['php_class'] !== $class->name && ! is_subclass_of($data['php_class'], $class->name)) {
  92. throw new \RuntimeException(
  93. "Row is of class '" . $data['php_class'] . "' which is not a subtype of expected " . $class->name
  94. );
  95. }
  96. $class = $this->cmf->getMetadataFor($data['php_class']);
  97. }
  98. unset($data['php_class']);
  99. $object = $this->tryGetById($class->name, $id);
  100. if ($object) {
  101. return $object;
  102. }
  103. $object = $class->newInstance();
  104. $oid = spl_object_hash($object);
  105. $this->originalData[$oid] = $data;
  106. $data = $this->idConverter->unserialize($class, $data);
  107. foreach ($data as $property => $value) {
  108. if (isset($class->reflFields[$property])) {
  109. $class->reflFields[$property]->setValue($object, $value);
  110. } else {
  111. $object->$property = $value;
  112. }
  113. }
  114. $idHash = $this->idHandler->hash($id);
  115. $this->identityMap[$class->name][$idHash] = $object;
  116. $this->identifiers[$oid] = $id;
  117. return $object;
  118. }
  119. private function computeChangeSet($class, $object)
  120. {
  121. $snapshot = $this->getObjectSnapshot($class, $object);
  122. $changeSet = [];
  123. $originalData = $this->originalData[spl_object_hash($object)];
  124. foreach ($snapshot as $field => $value) {
  125. if (! isset($originalData[$field]) || $originalData[$field] !== $value) {
  126. $changeSet[$field] = $value;
  127. }
  128. }
  129. if ($changeSet && ! $this->storageDriver->supportsPartialUpdates()) {
  130. $changeSet = array_merge($originalData, $changeSet);
  131. }
  132. return $changeSet;
  133. }
  134. private function getObjectSnapshot($class, $object)
  135. {
  136. $data = [];
  137. foreach ($class->reflFields as $fieldName => $reflProperty) {
  138. if (! isset($class->fields[$fieldName]['id'])) {
  139. $data[$fieldName] = $reflProperty->getValue($object);
  140. }
  141. }
  142. foreach (get_object_vars($object) as $property => $value) {
  143. if (! isset($data[$property])) {
  144. $data[$property] = $value;
  145. }
  146. }
  147. return $data;
  148. }
  149. public function scheduleForInsert($object)
  150. {
  151. $oid = spl_object_hash($object);
  152. if (isset($this->identifiers[$oid])) {
  153. return;
  154. }
  155. $class = $this->cmf->getMetadataFor(get_class($object));
  156. $id = $this->idHandler->getIdentifier($class, $object);
  157. if (! $id) {
  158. throw new \RuntimeException('Trying to persist entity that has no id.');
  159. }
  160. $idHash = $this->idHandler->hash($id);
  161. if (isset($this->identityMap[$class->name][$idHash])) {
  162. throw new \RuntimeException('Object with ID already exists.');
  163. }
  164. $this->scheduledInsertions[$oid] = $object;
  165. $this->identityMap[$class->name][$idHash] = $object;
  166. }
  167. public function scheduleForDelete($object)
  168. {
  169. $oid = spl_object_hash($object);
  170. if (! isset($this->identifiers[$oid])) {
  171. throw new \RuntimeException(
  172. 'Object scheduled for deletion is not managed. Only managed objects can be deleted.'
  173. );
  174. }
  175. $this->scheduledDeletions[$oid] = $object;
  176. }
  177. private function processIdentityMap()
  178. {
  179. foreach ($this->identityMap as $className => $entities) {
  180. foreach ($entities as $object) {
  181. $hash = spl_object_hash($object);
  182. if (isset($this->scheduledInsertions[$hash])) {
  183. continue;
  184. }
  185. $metadata = $this->cmf->getMetadataFor($className);
  186. $changeSet = $this->computeChangeSet($metadata, $object);
  187. if ($changeSet) {
  188. $changeSet['php_class'] = $metadata->name;
  189. $this->storageDriver->update($metadata->storageName, $this->identifiers[$hash], $changeSet);
  190. if ($this->storageDriver->supportsPartialUpdates()) {
  191. $this->originalData[$hash] = array_merge($this->originalData[$hash], $changeSet);
  192. } else {
  193. $this->originalData[$hash] = $changeSet;
  194. }
  195. }
  196. }
  197. }
  198. }
  199. private function processInsertions()
  200. {
  201. foreach ($this->scheduledInsertions as $object) {
  202. $class = $this->cmf->getMetadataFor(get_class($object));
  203. $id = $this->idHandler->getIdentifier($class, $object);
  204. $id = $this->idConverter->serialize($class, $id);
  205. if (! $id) {
  206. throw new \RuntimeException('Trying to persist entity that has no id.');
  207. }
  208. $data = $this->getObjectSnapshot($class, $object);
  209. $data['php_class'] = $class->name;
  210. $oid = spl_object_hash($object);
  211. $idHash = $this->idHandler->hash($id);
  212. $this->storageDriver->insert($class->storageName, $id, $data);
  213. $this->originalData[$oid] = $data;
  214. $this->identifiers[$oid] = $id;
  215. $this->identityMap[$class->name][$idHash] = $object;
  216. }
  217. }
  218. private function processDeletions()
  219. {
  220. foreach ($this->scheduledDeletions as $object) {
  221. $class = $this->cmf->getMetadataFor(get_class($object));
  222. $oid = spl_object_hash($object);
  223. $id = $this->identifiers[$oid];
  224. $idHash = $this->idHandler->hash($id);
  225. $this->storageDriver->delete($class->storageName, $id);
  226. unset($this->identifiers[$oid], $this->originalData[$oid], $this->identityMap[$class->name][$idHash]);
  227. }
  228. }
  229. public function commit()
  230. {
  231. $this->processIdentityMap();
  232. $this->processInsertions();
  233. $this->processDeletions();
  234. $this->scheduledInsertions = [];
  235. $this->scheduledDeletions = [];
  236. }
  237. public function clear()
  238. {
  239. $this->scheduledInsertions = [];
  240. $this->scheduledDeletions = [];
  241. $this->identifiers = [];
  242. $this->originalData = [];
  243. $this->identityMap = [];
  244. }
  245. }