source: pro-violet-viettel/sourcecode/application/libraries/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @ 345

Last change on this file since 345 was 345, checked in by quyenla, 11 years ago

collaborator page

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