Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / orm / lib / Doctrine / ORM / Persisters / JoinedSubclassPersister.php
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
20 namespace Doctrine\ORM\Persisters;
21
22 use Doctrine\ORM\ORMException;
23 use Doctrine\ORM\Mapping\ClassMetadata;
24 use Doctrine\ORM\Query\ResultSetMapping;
25
26 use Doctrine\DBAL\LockMode;
27 use Doctrine\DBAL\Types\Type;
28
29 use Doctrine\Common\Collections\Criteria;
30
31 /**
32  * The joined subclass persister maps a single entity instance to several tables in the
33  * database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
34  *
35  * @author Roman Borschel <roman@code-factory.org>
36  * @author Benjamin Eberlei <kontakt@beberlei.de>
37  * @author Alexander <iam.asm89@gmail.com>
38  * @since 2.0
39  * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
40  */
41 class JoinedSubclassPersister extends AbstractEntityInheritancePersister
42 {
43     /**
44      * Map that maps column names to the table names that own them.
45      * This is mainly a temporary cache, used during a single request.
46      *
47      * @var array
48      */
49     private $_owningTableMap = array();
50
51     /**
52      * Map of table to quoted table names.
53      *
54      * @var array
55      */
56     private $_quotedTableMap = array();
57
58     /**
59      * {@inheritdoc}
60      */
61     protected function _getDiscriminatorColumnTableName()
62     {
63         $class = ($this->_class->name !== $this->_class->rootEntityName)
64             ? $this->_em->getClassMetadata($this->_class->rootEntityName)
65             : $this->_class;
66
67         return $class->getTableName();
68     }
69
70     /**
71      * This function finds the ClassMetadata instance in an inheritance hierarchy
72      * that is responsible for enabling versioning.
73      *
74      * @return \Doctrine\ORM\Mapping\ClassMetadata
75      */
76     private function _getVersionedClassMetadata()
77     {
78         if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) {
79             $definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited'];
80
81             return $this->_em->getClassMetadata($definingClassName);
82         }
83
84         return $this->_class;
85     }
86
87     /**
88      * Gets the name of the table that owns the column the given field is mapped to.
89      *
90      * @param string $fieldName
91      * @return string
92      * @override
93      */
94     public function getOwningTable($fieldName)
95     {
96         if (isset($this->_owningTableMap[$fieldName])) {
97             return $this->_owningTableMap[$fieldName];
98         }
99
100         if (isset($this->_class->associationMappings[$fieldName]['inherited'])) {
101             $cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']);
102         } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) {
103             $cm = $this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited']);
104         } else {
105             $cm = $this->_class;
106         }
107
108         $tableName = $cm->getTableName();
109
110         $this->_owningTableMap[$fieldName] = $tableName;
111         $this->_quotedTableMap[$tableName] = $this->quoteStrategy->getTableName($cm, $this->_platform);
112
113         return $tableName;
114     }
115
116     /**
117      * {@inheritdoc}
118      */
119     public function executeInserts()
120     {
121         if ( ! $this->_queuedInserts) {
122             return;
123         }
124
125         $postInsertIds = array();
126         $idGen = $this->_class->idGenerator;
127         $isPostInsertId = $idGen->isPostInsertGenerator();
128
129         // Prepare statement for the root table
130         $rootClass     = ($this->_class->name !== $this->_class->rootEntityName) ? $this->_em->getClassMetadata($this->_class->rootEntityName) : $this->_class;
131         $rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name);
132         $rootTableName = $rootClass->getTableName();
133         $rootTableStmt = $this->_conn->prepare($rootPersister->_getInsertSQL());
134
135         // Prepare statements for sub tables.
136         $subTableStmts = array();
137
138         if ($rootClass !== $this->_class) {
139             $subTableStmts[$this->_class->getTableName()] = $this->_conn->prepare($this->_getInsertSQL());
140         }
141
142         foreach ($this->_class->parentClasses as $parentClassName) {
143             $parentClass = $this->_em->getClassMetadata($parentClassName);
144             $parentTableName = $parentClass->getTableName();
145
146             if ($parentClass !== $rootClass) {
147                 $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName);
148                 $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL());
149             }
150         }
151
152         // Execute all inserts. For each entity:
153         // 1) Insert on root table
154         // 2) Insert on sub tables
155         foreach ($this->_queuedInserts as $entity) {
156             $insertData = $this->_prepareInsertData($entity);
157
158             // Execute insert on root table
159             $paramIndex = 1;
160
161             foreach ($insertData[$rootTableName] as $columnName => $value) {
162                 $rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
163             }
164
165             $rootTableStmt->execute();
166
167             if ($isPostInsertId) {
168                 $id = $idGen->generate($this->_em, $entity);
169                 $postInsertIds[$id] = $entity;
170             } else {
171                 $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
172             }
173
174             // Execute inserts on subtables.
175             // The order doesn't matter because all child tables link to the root table via FK.
176             foreach ($subTableStmts as $tableName => $stmt) {
177                 $data = isset($insertData[$tableName]) ? $insertData[$tableName] : array();
178                 $paramIndex = 1;
179
180                 foreach ((array) $id as $idName => $idVal) {
181                     $type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING;
182
183                     $stmt->bindValue($paramIndex++, $idVal, $type);
184                 }
185
186                 foreach ($data as $columnName => $value) {
187                     $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
188                 }
189
190                 $stmt->execute();
191             }
192         }
193
194         $rootTableStmt->closeCursor();
195
196         foreach ($subTableStmts as $stmt) {
197             $stmt->closeCursor();
198         }
199
200         if ($this->_class->isVersioned) {
201             $this->assignDefaultVersionValue($entity, $id);
202         }
203
204         $this->_queuedInserts = array();
205
206         return $postInsertIds;
207     }
208
209     /**
210      * {@inheritdoc}
211      */
212     public function update($entity)
213     {
214         $updateData = $this->_prepareUpdateData($entity);
215
216         if (($isVersioned = $this->_class->isVersioned) != false) {
217             $versionedClass = $this->_getVersionedClassMetadata();
218             $versionedTable = $versionedClass->getTableName();
219         }
220
221         if ($updateData) {
222             foreach ($updateData as $tableName => $data) {
223                 $this->_updateTable(
224                     $entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName
225                 );
226             }
227
228             // Make sure the table with the version column is updated even if no columns on that
229             // table were affected.
230             if ($isVersioned && ! isset($updateData[$versionedTable])) {
231                 $this->_updateTable($entity, $this->quoteStrategy->getTableName($versionedClass, $this->_platform), array(), true);
232
233                 $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
234                 $this->assignDefaultVersionValue($entity, $id);
235             }
236         }
237     }
238
239     /**
240      * {@inheritdoc}
241      */
242     public function delete($entity)
243     {
244         $identifier = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
245         $this->deleteJoinTableRecords($identifier);
246
247         $id = array_combine($this->_class->getIdentifierColumnNames(), $identifier);
248
249         // If the database platform supports FKs, just
250         // delete the row from the root table. Cascades do the rest.
251         if ($this->_platform->supportsForeignKeyConstraints()) {
252             $this->_conn->delete(
253                 $this->quoteStrategy->getTableName($this->_em->getClassMetadata($this->_class->rootEntityName), $this->_platform), $id
254             );
255         } else {
256             // Delete from all tables individually, starting from this class' table up to the root table.
257             $this->_conn->delete($this->quoteStrategy->getTableName($this->_class, $this->_platform), $id);
258
259             foreach ($this->_class->parentClasses as $parentClass) {
260                 $this->_conn->delete(
261                     $this->quoteStrategy->getTableName($this->_em->getClassMetadata($parentClass), $this->_platform), $id
262                 );
263             }
264         }
265     }
266
267     /**
268      * {@inheritdoc}
269      */
270     protected function _getSelectEntitiesSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
271     {
272         $idColumns = $this->_class->getIdentifierColumnNames();
273         $baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
274
275         // Create the column list fragment only once
276         if ($this->_selectColumnListSql === null) {
277
278             $this->_rsm = new ResultSetMapping();
279             $this->_rsm->addEntityResult($this->_class->name, 'r');
280
281             // Add regular columns
282             $columnList = '';
283
284             foreach ($this->_class->fieldMappings as $fieldName => $mapping) {
285                 if ($columnList != '') $columnList .= ', ';
286
287                 $columnList .= $this->_getSelectColumnSQL(
288                     $fieldName,
289                     isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class
290                 );
291             }
292
293             // Add foreign key columns
294             foreach ($this->_class->associationMappings as $assoc2) {
295                 if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) {
296                     $tableAlias = isset($assoc2['inherited']) ? $this->_getSQLTableAlias($assoc2['inherited']) : $baseTableAlias;
297
298                     foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
299                         if ($columnList != '') $columnList .= ', ';
300
301                         $columnList .= $this->getSelectJoinColumnSQL(
302                             $tableAlias,
303                             $srcColumn,
304                             isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name
305                         );
306                     }
307                 }
308             }
309
310             // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#_processSQLResult).
311             $discrColumn = $this->_class->discriminatorColumn['name'];
312             $tableAlias  = ($this->_class->rootEntityName == $this->_class->name) ? $baseTableAlias : $this->_getSQLTableAlias($this->_class->rootEntityName);
313             $columnList .= ', ' . $tableAlias . '.' . $discrColumn;
314
315             $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
316
317             $this->_rsm->setDiscriminatorColumn('r', $resultColumnName);
318             $this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
319         }
320
321         // INNER JOIN parent tables
322         $joinSql = '';
323
324         foreach ($this->_class->parentClasses as $parentClassName) {
325             $parentClass = $this->_em->getClassMetadata($parentClassName);
326             $tableAlias = $this->_getSQLTableAlias($parentClassName);
327             $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->_platform) . ' ' . $tableAlias . ' ON ';
328             $first = true;
329
330             foreach ($idColumns as $idColumn) {
331                 if ($first) $first = false; else $joinSql .= ' AND ';
332
333                 $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
334             }
335         }
336
337         // OUTER JOIN sub tables
338         foreach ($this->_class->subClasses as $subClassName) {
339             $subClass = $this->_em->getClassMetadata($subClassName);
340             $tableAlias = $this->_getSQLTableAlias($subClassName);
341
342             if ($this->_selectColumnListSql === null) {
343                 // Add subclass columns
344                 foreach ($subClass->fieldMappings as $fieldName => $mapping) {
345                     if (isset($mapping['inherited'])) continue;
346
347                     $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass);
348                 }
349
350                 // Add join columns (foreign keys)
351                 foreach ($subClass->associationMappings as $assoc2) {
352                     if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE && ! isset($assoc2['inherited'])) {
353                         foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
354                             if ($columnList != '') $columnList .= ', ';
355
356                             $columnList .= $this->getSelectJoinColumnSQL(
357                                 $tableAlias,
358                                 $srcColumn,
359                                 isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name
360                             );
361                         }
362                     }
363                 }
364             }
365
366             // Add LEFT JOIN
367             $joinSql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->_platform) . ' ' . $tableAlias . ' ON ';
368             $first = true;
369
370             foreach ($idColumns as $idColumn) {
371                 if ($first) $first = false; else $joinSql .= ' AND ';
372
373                 $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
374             }
375         }
376
377         $joinSql .= ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : '';
378
379         $conditionSql = ($criteria instanceof Criteria)
380             ? $this->_getSelectConditionCriteriaSQL($criteria)
381             : $this->_getSelectConditionSQL($criteria, $assoc);
382
383         // If the current class in the root entity, add the filters
384         if ($filterSql = $this->generateFilterConditionSQL($this->_em->getClassMetadata($this->_class->rootEntityName), $this->_getSQLTableAlias($this->_class->rootEntityName))) {
385             if ($conditionSql) {
386                 $conditionSql .= ' AND ';
387             }
388
389             $conditionSql .= $filterSql;
390         }
391
392         $orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
393         $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : '';
394
395         if ($this->_selectColumnListSql === null) {
396             $this->_selectColumnListSql = $columnList;
397         }
398
399         $lockSql = '';
400
401         if ($lockMode == LockMode::PESSIMISTIC_READ) {
402             $lockSql = ' ' . $this->_platform->getReadLockSql();
403         } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
404             $lockSql = ' ' . $this->_platform->getWriteLockSql();
405         }
406
407         return $this->_platform->modifyLimitQuery('SELECT ' . $this->_selectColumnListSql
408                 . ' FROM ' . $this->quoteStrategy->getTableName($this->_class, $this->_platform) . ' ' . $baseTableAlias
409                 . $joinSql
410                 . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql, $limit, $offset)
411                 . $lockSql;
412     }
413
414     /**
415      * Get the FROM and optionally JOIN conditions to lock the entity managed by this persister.
416      *
417      * @return string
418      */
419     public function getLockTablesSql()
420     {
421         $idColumns = $this->_class->getIdentifierColumnNames();
422         $baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
423
424         // INNER JOIN parent tables
425         $joinSql = '';
426
427         foreach ($this->_class->parentClasses as $parentClassName) {
428             $parentClass = $this->_em->getClassMetadata($parentClassName);
429             $tableAlias = $this->_getSQLTableAlias($parentClassName);
430             $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->_platform) . ' ' . $tableAlias . ' ON ';
431             $first = true;
432
433             foreach ($idColumns as $idColumn) {
434                 if ($first) $first = false; else $joinSql .= ' AND ';
435
436                 $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
437             }
438         }
439
440         return 'FROM ' .$this->quoteStrategy->getTableName($this->_class, $this->_platform) . ' ' . $baseTableAlias . $joinSql;
441     }
442
443     /* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */
444     protected function _getSelectColumnListSQL()
445     {
446         throw new \BadMethodCallException("Illegal invocation of ".__METHOD__.".");
447     }
448
449     /** {@inheritdoc} */
450     protected function _getInsertColumnList()
451     {
452         // Identifier columns must always come first in the column list of subclasses.
453         $columns = $this->_class->parentClasses ? $this->_class->getIdentifierColumnNames() : array();
454
455         foreach ($this->_class->reflFields as $name => $field) {
456             if (isset($this->_class->fieldMappings[$name]['inherited']) && ! isset($this->_class->fieldMappings[$name]['id'])
457                     || isset($this->_class->associationMappings[$name]['inherited'])
458                     || ($this->_class->isVersioned && $this->_class->versionField == $name)) {
459                 continue;
460             }
461
462             if (isset($this->_class->associationMappings[$name])) {
463                 $assoc = $this->_class->associationMappings[$name];
464                 if ($assoc['type'] & ClassMetadata::TO_ONE && $assoc['isOwningSide']) {
465                     foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) {
466                         $columns[] = $sourceCol;
467                     }
468                 }
469             } else if ($this->_class->name != $this->_class->rootEntityName ||
470                     ! $this->_class->isIdGeneratorIdentity() || $this->_class->identifier[0] != $name) {
471                 $columns[] = $this->quoteStrategy->getColumnName($name, $this->_class, $this->_platform);
472             }
473         }
474
475         // Add discriminator column if it is the topmost class.
476         if ($this->_class->name == $this->_class->rootEntityName) {
477             $columns[] = $this->_class->discriminatorColumn['name'];
478         }
479
480         return $columns;
481     }
482
483     /**
484      * {@inheritdoc}
485      */
486     protected function assignDefaultVersionValue($entity, $id)
487     {
488         $value = $this->fetchVersionValue($this->_getVersionedClassMetadata(), $id);
489         $this->_class->setFieldValue($entity, $this->_class->versionField, $value);
490     }
491
492 }