ManyToManyPersister.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <?php
  2. /*
  3. * $Id$
  4. *
  5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. *
  17. * This software consists of voluntary contributions made by many individuals
  18. * and is licensed under the LGPL. For more information, see
  19. * <http://www.doctrine-project.org>.
  20. */
  21. namespace Doctrine\ORM\Persisters;
  22. use Doctrine\ORM\PersistentCollection,
  23. Doctrine\ORM\UnitOfWork;
  24. /**
  25. * Persister for many-to-many collections.
  26. *
  27. * @author Roman Borschel <roman@code-factory.org>
  28. * @since 2.0
  29. */
  30. class ManyToManyPersister extends AbstractCollectionPersister
  31. {
  32. /**
  33. * {@inheritdoc}
  34. *
  35. * @override
  36. */
  37. protected function _getDeleteRowSQL(PersistentCollection $coll)
  38. {
  39. $mapping = $coll->getMapping();
  40. $joinTable = $mapping['joinTable'];
  41. $columns = $mapping['joinTableColumns'];
  42. return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
  43. }
  44. /**
  45. * {@inheritdoc}
  46. *
  47. * @override
  48. * @internal Order of the parameters must be the same as the order of the columns in
  49. * _getDeleteRowSql.
  50. */
  51. protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element)
  52. {
  53. return $this->_collectJoinTableColumnParameters($coll, $element);
  54. }
  55. /**
  56. * {@inheritdoc}
  57. *
  58. * @override
  59. */
  60. protected function _getUpdateRowSQL(PersistentCollection $coll)
  61. {}
  62. /**
  63. * {@inheritdoc}
  64. *
  65. * @override
  66. * @internal Order of the parameters must be the same as the order of the columns in
  67. * _getInsertRowSql.
  68. */
  69. protected function _getInsertRowSQL(PersistentCollection $coll)
  70. {
  71. $mapping = $coll->getMapping();
  72. $joinTable = $mapping['joinTable'];
  73. $columns = $mapping['joinTableColumns'];
  74. return 'INSERT INTO ' . $joinTable['name'] . ' (' . implode(', ', $columns) . ')'
  75. . ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
  76. }
  77. /**
  78. * {@inheritdoc}
  79. *
  80. * @override
  81. * @internal Order of the parameters must be the same as the order of the columns in
  82. * _getInsertRowSql.
  83. */
  84. protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element)
  85. {
  86. return $this->_collectJoinTableColumnParameters($coll, $element);
  87. }
  88. /**
  89. * Collects the parameters for inserting/deleting on the join table in the order
  90. * of the join table columns as specified in ManyToManyMapping#joinTableColumns.
  91. *
  92. * @param $coll
  93. * @param $element
  94. * @return array
  95. */
  96. private function _collectJoinTableColumnParameters(PersistentCollection $coll, $element)
  97. {
  98. $params = array();
  99. $mapping = $coll->getMapping();
  100. $isComposite = count($mapping['joinTableColumns']) > 2;
  101. $identifier1 = $this->_uow->getEntityIdentifier($coll->getOwner());
  102. $identifier2 = $this->_uow->getEntityIdentifier($element);
  103. if ($isComposite) {
  104. $class1 = $this->_em->getClassMetadata(get_class($coll->getOwner()));
  105. $class2 = $coll->getTypeClass();
  106. }
  107. foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
  108. if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) {
  109. if ($isComposite) {
  110. if ($class1->containsForeignIdentifier) {
  111. $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
  112. } else {
  113. $params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
  114. }
  115. } else {
  116. $params[] = array_pop($identifier1);
  117. }
  118. } else {
  119. if ($isComposite) {
  120. if ($class2->containsForeignIdentifier) {
  121. $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
  122. } else {
  123. $params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
  124. }
  125. } else {
  126. $params[] = array_pop($identifier2);
  127. }
  128. }
  129. }
  130. return $params;
  131. }
  132. /**
  133. * {@inheritdoc}
  134. *
  135. * @override
  136. */
  137. protected function _getDeleteSQL(PersistentCollection $coll)
  138. {
  139. $mapping = $coll->getMapping();
  140. $joinTable = $mapping['joinTable'];
  141. $whereClause = '';
  142. foreach ($mapping['relationToSourceKeyColumns'] as $relationColumn => $srcColumn) {
  143. if ($whereClause !== '') $whereClause .= ' AND ';
  144. $whereClause .= "$relationColumn = ?";
  145. }
  146. return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
  147. }
  148. /**
  149. * {@inheritdoc}
  150. *
  151. * @override
  152. * @internal Order of the parameters must be the same as the order of the columns in
  153. * _getDeleteSql.
  154. */
  155. protected function _getDeleteSQLParameters(PersistentCollection $coll)
  156. {
  157. $params = array();
  158. $mapping = $coll->getMapping();
  159. $identifier = $this->_uow->getEntityIdentifier($coll->getOwner());
  160. if (count($mapping['relationToSourceKeyColumns']) > 1) {
  161. $sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner()));
  162. foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) {
  163. $params[] = $identifier[$sourceClass->fieldNames[$srcColumn]];
  164. }
  165. } else {
  166. $params[] = array_pop($identifier);
  167. }
  168. return $params;
  169. }
  170. /**
  171. * {@inheritdoc}
  172. */
  173. public function count(PersistentCollection $coll)
  174. {
  175. $params = array();
  176. $mapping = $coll->getMapping();
  177. $class = $this->_em->getClassMetadata($mapping['sourceEntity']);
  178. $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
  179. if ($mapping['isOwningSide']) {
  180. $joinTable = $mapping['joinTable'];
  181. $joinColumns = $mapping['relationToSourceKeyColumns'];
  182. } else {
  183. $mapping = $this->_em->getClassMetadata($mapping['targetEntity'])->associationMappings[$mapping['mappedBy']];
  184. $joinTable = $mapping['joinTable'];
  185. $joinColumns = $mapping['relationToTargetKeyColumns'];
  186. }
  187. $whereClause = '';
  188. foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
  189. if (isset($joinColumns[$joinTableColumn])) {
  190. if ($whereClause !== '') {
  191. $whereClause .= ' AND ';
  192. }
  193. $whereClause .= "$joinTableColumn = ?";
  194. if ($class->containsForeignIdentifier) {
  195. $params[] = $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])];
  196. } else {
  197. $params[] = $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
  198. }
  199. }
  200. }
  201. $sql = 'SELECT count(*) FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
  202. return $this->_conn->fetchColumn($sql, $params);
  203. }
  204. /**
  205. * @param PersistentCollection $coll
  206. * @param int $offset
  207. * @param int $length
  208. * @return array
  209. */
  210. public function slice(PersistentCollection $coll, $offset, $length = null)
  211. {
  212. $mapping = $coll->getMapping();
  213. return $this->_em->getUnitOfWork()
  214. ->getEntityPersister($mapping['targetEntity'])
  215. ->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length);
  216. }
  217. /**
  218. * @param PersistentCollection $coll
  219. * @param object $element
  220. */
  221. public function contains(PersistentCollection $coll, $element)
  222. {
  223. $uow = $this->_em->getUnitOfWork();
  224. // shortcut for new entities
  225. if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
  226. return false;
  227. }
  228. $params = array();
  229. $mapping = $coll->getMapping();
  230. if (!$mapping['isOwningSide']) {
  231. $sourceClass = $this->_em->getClassMetadata($mapping['targetEntity']);
  232. $targetClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
  233. $sourceId = $uow->getEntityIdentifier($element);
  234. $targetId = $uow->getEntityIdentifier($coll->getOwner());
  235. $mapping = $sourceClass->associationMappings[$mapping['mappedBy']];
  236. } else {
  237. $sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
  238. $targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);
  239. $sourceId = $uow->getEntityIdentifier($coll->getOwner());
  240. $targetId = $uow->getEntityIdentifier($element);
  241. }
  242. $joinTable = $mapping['joinTable'];
  243. $whereClause = '';
  244. foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
  245. if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
  246. if ($whereClause !== '') {
  247. $whereClause .= ' AND ';
  248. }
  249. $whereClause .= "$joinTableColumn = ?";
  250. if ($targetClass->containsForeignIdentifier) {
  251. $params[] = $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
  252. } else {
  253. $params[] = $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
  254. }
  255. } else if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) {
  256. if ($whereClause !== '') {
  257. $whereClause .= ' AND ';
  258. }
  259. $whereClause .= "$joinTableColumn = ?";
  260. if ($sourceClass->containsForeignIdentifier) {
  261. $params[] = $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
  262. } else {
  263. $params[] = $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
  264. }
  265. }
  266. }
  267. $sql = 'SELECT 1 FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
  268. return (bool)$this->_conn->fetchColumn($sql, $params);
  269. }
  270. }