PageRenderTime 23ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/library/Doctrine/ODM/MongoDB/Persisters/PersistenceBuilder.php

https://github.com/akentner/LexsignExtensions
PHP | 337 lines | 187 code | 45 blank | 105 comment | 60 complexity | 47de89ab8b59df6b10aed743a75f9b29 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 LGPL. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\ODM\MongoDB\Persisters;
  20. use Doctrine\ODM\MongoDB\DocumentManager,
  21. Doctrine\ODM\MongoDB\UnitOfWork,
  22. Doctrine\ODM\MongoDB\Mapping\ClassMetadata,
  23. Doctrine\ODM\MongoDB\Mapping\Types\Type;
  24. /**
  25. * PersistenceBuilder builds the queries used by the persisters to update and insert
  26. * documents when a DocumentManager is flushed. It uses the changeset information in the
  27. * UnitOfWork to build queries using atomic operators like $set, $unset, etc.
  28. *
  29. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  30. * @link www.doctrine-project.com
  31. * @since 1.0
  32. * @author Jonathan H. Wage <jonwage@gmail.com>
  33. */
  34. class PersistenceBuilder
  35. {
  36. /**
  37. * The DocumentManager instance.
  38. *
  39. * @var Doctrine\ODM\MongoDB\DocumentManager
  40. */
  41. private $dm;
  42. /**
  43. * The UnitOfWork instance.
  44. *
  45. * @var Doctrine\ODM\MongoDB\UnitOfWork
  46. */
  47. private $uow;
  48. /**
  49. * Initializes a new PersistenceBuilder instance.
  50. *
  51. * @param Doctrine\ODM\MongoDB\DocumentManager $dm
  52. * @param Doctrine\ODM\MongoDB\UnitOfWork $uow
  53. */
  54. public function __construct(DocumentManager $dm, UnitOfWork $uow, $cmd)
  55. {
  56. $this->dm = $dm;
  57. $this->uow = $uow;
  58. $this->cmd = $cmd;
  59. }
  60. /**
  61. * Prepares the array that is ready to be inserted to mongodb for a given object document.
  62. *
  63. * @param object $document
  64. * @return array $insertData
  65. */
  66. public function prepareInsertData($document)
  67. {
  68. $class = $this->dm->getClassMetadata(get_class($document));
  69. $changeset = $this->uow->getDocumentChangeSet($document);
  70. $insertData = array();
  71. foreach ($class->fieldMappings as $mapping) {
  72. // many collections are inserted later
  73. if ($mapping['type'] === ClassMetadata::MANY) {
  74. continue;
  75. }
  76. // skip not saved fields
  77. if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) {
  78. continue;
  79. }
  80. // Skip version and lock fields
  81. if (isset($mapping['version']) || isset($mapping['lock'])) {
  82. continue;
  83. }
  84. $new = isset($changeset[$mapping['fieldName']][1]) ? $changeset[$mapping['fieldName']][1] : null;
  85. // Generate a document identifier
  86. if ($new === null && $class->identifier === $mapping['fieldName'] && $class->generatorType !== ClassMetadata::GENERATOR_TYPE_NONE) {
  87. $new = $class->idGenerator->generate($this->dm, $document);
  88. }
  89. // Don't store null values unless nullable === true
  90. if ($new === null && $mapping['nullable'] === false) {
  91. continue;
  92. }
  93. $value = null;
  94. if ($new !== null) {
  95. // @Field, @String, @Date, etc.
  96. if ( ! isset($mapping['association'])) {
  97. $value = Type::getType($mapping['type'])->convertToDatabaseValue($new);
  98. // @ReferenceOne
  99. } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) {
  100. if ($mapping['isInverseSide']) {
  101. continue;
  102. }
  103. $oid = spl_object_hash($new);
  104. if ($this->isScheduledForInsert($new)) {
  105. // The associated document $new is not yet persisted, so we must
  106. // set $new = null, in order to insert a null value and schedule an
  107. // extra update on the UnitOfWork.
  108. $this->uow->scheduleExtraUpdate($document, array(
  109. $mapping['fieldName'] => array(null, $new)
  110. ));
  111. } else {
  112. $value = $this->prepareReferencedDocumentValue($mapping, $new);
  113. }
  114. // @EmbedOne
  115. } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::EMBED_ONE) {
  116. $value = $this->prepareEmbeddedDocumentValue($mapping, $new);
  117. // @ReferenceMany
  118. } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_MANY) {
  119. $value = array();
  120. foreach ($new as $reference) {
  121. $value[] = $this->prepareReferenceDocValue($mapping, $reference);
  122. }
  123. // @EmbedMany
  124. } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::EMBED_MANY) {
  125. $value = array();
  126. foreach ($new as $reference) {
  127. $value[] = $this->prepareEmbeddedDocumentValue($mapping, $reference);
  128. }
  129. }
  130. }
  131. $insertData[$mapping['name']] = $value;
  132. }
  133. // add discriminator if the class has one
  134. if ($class->hasDiscriminator()) {
  135. $insertData[$class->discriminatorField['name']] = $class->discriminatorValue;
  136. }
  137. return $insertData;
  138. }
  139. /**
  140. * Prepares the update query to update a given document object in mongodb.
  141. *
  142. * @param object $document
  143. * @return array $updateData
  144. */
  145. public function prepareUpdateData($document)
  146. {
  147. $oid = spl_object_hash($document);
  148. $class = $this->dm->getClassMetadata(get_class($document));
  149. $changeset = $this->uow->getDocumentChangeSet($document);
  150. $updateData = array();
  151. foreach ($changeset as $fieldName => $change) {
  152. $mapping = $class->fieldMappings[$fieldName];
  153. // skip not saved fields
  154. if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) {
  155. continue;
  156. }
  157. // Skip version and lock fields
  158. if (isset($mapping['version']) || isset($mapping['lock'])) {
  159. continue;
  160. }
  161. list($old, $new) = $change;
  162. // @Inc
  163. if ($mapping['type'] === 'increment') {
  164. if ($new >= $old) {
  165. $updateData[$this->cmd . 'inc'][$mapping['name']] = $new - $old;
  166. } else {
  167. $updateData[$this->cmd . 'inc'][$mapping['name']] = ($old - $new) * -1;
  168. }
  169. // @Field, @String, @Date, etc.
  170. } elseif ( ! isset($mapping['association'])) {
  171. if (isset($new) || $mapping['nullable'] === true) {
  172. $updateData[$this->cmd . 'set'][$mapping['name']] = Type::getType($mapping['type'])->convertToDatabaseValue($new);
  173. } else {
  174. $updateData[$this->cmd . 'unset'][$mapping['name']] = true;
  175. }
  176. // @EmbedOne
  177. } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::EMBED_ONE) {
  178. // If we have a new embedded document then lets set the whole thing
  179. if ($new && $this->uow->isScheduledForInsert($new)) {
  180. $updateData[$this->cmd . 'set'][$mapping['name']] = $this->prepareEmbeddedDocumentValue($mapping, $new);
  181. // If we don't have a new value then lets unset the embedded document
  182. } elseif ( ! $new) {
  183. $updateData[$this->cmd . 'unset'][$mapping['name']] = true;
  184. // Update existing embedded document
  185. } else {
  186. $update = $this->prepareUpdateData($new);
  187. foreach ($update as $cmd => $values) {
  188. foreach ($values as $key => $value) {
  189. $updateData[$cmd][$mapping['name'] . '.' . $key] = $value;
  190. }
  191. }
  192. }
  193. // @EmbedMany
  194. } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::EMBED_MANY) {
  195. foreach ($new as $key => $embeddedDoc) {
  196. if ( ! $this->uow->isScheduledForInsert($embeddedDoc)) {
  197. $update = $this->prepareUpdateData($embeddedDoc);
  198. foreach ($update as $cmd => $values) {
  199. foreach ($values as $name => $value) {
  200. $updateData[$cmd][$mapping['name'] . '.' . $key . '.' . $name] = $value;
  201. }
  202. }
  203. }
  204. }
  205. // @ReferenceOne
  206. } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isOwningSide']) {
  207. if (isset($new) || $mapping['nullable'] === true) {
  208. $updateData[$this->cmd . 'set'][$mapping['name']] = $this->prepareReferencedDocumentValue($mapping, $new);
  209. } else {
  210. $updateData[$this->cmd . 'unset'][$mapping['name']] = true;
  211. }
  212. // @ReferenceMany
  213. } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_MANY) {
  214. // Do nothing right now
  215. }
  216. }
  217. return $updateData;
  218. }
  219. /**
  220. * Returns the reference representation to be stored in mongodb or null if not applicable.
  221. *
  222. * @param array $referenceMapping
  223. * @param object $document
  224. * @return array $referenceDocumentValue
  225. */
  226. public function prepareReferencedDocumentValue(array $referenceMapping, $document)
  227. {
  228. return $this->dm->createDBRef($document, $referenceMapping);
  229. }
  230. /**
  231. * Prepares array of values to be stored in mongo to represent embedded object.
  232. *
  233. * @param array $embeddedMapping
  234. * @param object $embeddedDocument
  235. * @return array|object $embeddedDocumentValue
  236. */
  237. public function prepareEmbeddedDocumentValue(array $embeddedMapping, $embeddedDocument)
  238. {
  239. $className = get_class($embeddedDocument);
  240. $class = $this->dm->getClassMetadata($className);
  241. $embeddedDocumentValue = array();
  242. foreach ($class->fieldMappings as $mapping) {
  243. // Skip not saved fields
  244. if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) {
  245. continue;
  246. }
  247. $rawValue = $class->reflFields[$mapping['fieldName']]->getValue($embeddedDocument);
  248. // Generate a document identifier
  249. if ($rawValue === null && $class->identifier === $mapping['fieldName'] && $class->generatorType !== ClassMetadata::GENERATOR_TYPE_NONE) {
  250. $rawValue = $class->idGenerator->generate($this->dm, $embeddedDocument);
  251. $class->setIdentifierValue($embeddedDocument, $rawValue);
  252. }
  253. $value = null;
  254. if ($rawValue !== null) {
  255. /** @Field, @String, @Date, etc. */
  256. if ( ! isset($mapping['association'])) {
  257. $value = Type::getType($mapping['type'])->convertToDatabaseValue($rawValue);
  258. /** @EmbedOne */
  259. } elseif (isset($mapping['association']) && $mapping['association'] == ClassMetadata::EMBED_ONE) {
  260. $value = $this->prepareEmbeddedDocumentValue($mapping, $rawValue);
  261. /** @EmbedMany */
  262. } elseif (isset($mapping['association']) && $mapping['association'] == ClassMetadata::EMBED_MANY) {
  263. // do nothing for embedded many
  264. // CollectionPersister will take care of this
  265. /** @ReferenceOne */
  266. } elseif (isset($mapping['association']) && $mapping['association'] == ClassMetadata::REFERENCE_ONE) {
  267. $value = $this->prepareReferencedDocumentValue($mapping, $rawValue);
  268. }
  269. }
  270. if ($value === null && $mapping['nullable'] === false) {
  271. continue;
  272. }
  273. $embeddedDocumentValue[$mapping['name']] = $value;
  274. }
  275. // Store a discriminator value if the embedded document is not mapped explicitely to a targetDocument
  276. if ( ! isset($embeddedMapping['targetDocument'])) {
  277. $discriminatorField = isset($embeddedMapping['discriminatorField']) ? $embeddedMapping['discriminatorField'] : '_doctrine_class_name';
  278. $discriminatorValue = isset($embeddedMapping['discriminatorMap']) ? array_search($class->getName(), $embeddedMapping['discriminatorMap']) : $class->getName();
  279. $embeddedDocumentValue[$discriminatorField] = $discriminatorValue;
  280. }
  281. // Fix so that we can force empty embedded document to store itself as a hash instead of an array
  282. if (empty($embeddedDocumentValue)) {
  283. return (object) $embeddedDocumentValue;
  284. }
  285. return $embeddedDocumentValue;
  286. }
  287. private function isScheduledForInsert($document)
  288. {
  289. return $this->uow->isScheduledForInsert($document)
  290. || $this->uow->getDocumentPersister(get_class($document))->isQueuedForInsert($document);
  291. }
  292. }