X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=vendor%2Fdoctrine%2Form%2Flib%2FDoctrine%2FORM%2FPersisters%2FJoinedSubclassPersister.php;fp=vendor%2Fdoctrine%2Form%2Flib%2FDoctrine%2FORM%2FPersisters%2FJoinedSubclassPersister.php;h=431b237a61a3b0089adf69871790b827497802e8;hb=8b04b2d11798dee4f3e1358e4f43e97a6df851f6;hp=0000000000000000000000000000000000000000;hpb=73568cf05a785a45f94ca3f2351d9e07bf917958;p=zf2.biz%2Fapplication_blanche.git diff --git a/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php new file mode 100644 index 0000000..431b237 --- /dev/null +++ b/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -0,0 +1,492 @@ +. + */ + +namespace Doctrine\ORM\Persisters; + +use Doctrine\ORM\ORMException; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Query\ResultSetMapping; + +use Doctrine\DBAL\LockMode; +use Doctrine\DBAL\Types\Type; + +use Doctrine\Common\Collections\Criteria; + +/** + * The joined subclass persister maps a single entity instance to several tables in the + * database as it is defined by the Class Table Inheritance strategy. + * + * @author Roman Borschel + * @author Benjamin Eberlei + * @author Alexander + * @since 2.0 + * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html + */ +class JoinedSubclassPersister extends AbstractEntityInheritancePersister +{ + /** + * Map that maps column names to the table names that own them. + * This is mainly a temporary cache, used during a single request. + * + * @var array + */ + private $_owningTableMap = array(); + + /** + * Map of table to quoted table names. + * + * @var array + */ + private $_quotedTableMap = array(); + + /** + * {@inheritdoc} + */ + protected function _getDiscriminatorColumnTableName() + { + $class = ($this->_class->name !== $this->_class->rootEntityName) + ? $this->_em->getClassMetadata($this->_class->rootEntityName) + : $this->_class; + + return $class->getTableName(); + } + + /** + * This function finds the ClassMetadata instance in an inheritance hierarchy + * that is responsible for enabling versioning. + * + * @return \Doctrine\ORM\Mapping\ClassMetadata + */ + private function _getVersionedClassMetadata() + { + if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) { + $definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited']; + + return $this->_em->getClassMetadata($definingClassName); + } + + return $this->_class; + } + + /** + * Gets the name of the table that owns the column the given field is mapped to. + * + * @param string $fieldName + * @return string + * @override + */ + public function getOwningTable($fieldName) + { + if (isset($this->_owningTableMap[$fieldName])) { + return $this->_owningTableMap[$fieldName]; + } + + if (isset($this->_class->associationMappings[$fieldName]['inherited'])) { + $cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']); + } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { + $cm = $this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited']); + } else { + $cm = $this->_class; + } + + $tableName = $cm->getTableName(); + + $this->_owningTableMap[$fieldName] = $tableName; + $this->_quotedTableMap[$tableName] = $this->quoteStrategy->getTableName($cm, $this->_platform); + + return $tableName; + } + + /** + * {@inheritdoc} + */ + public function executeInserts() + { + if ( ! $this->_queuedInserts) { + return; + } + + $postInsertIds = array(); + $idGen = $this->_class->idGenerator; + $isPostInsertId = $idGen->isPostInsertGenerator(); + + // Prepare statement for the root table + $rootClass = ($this->_class->name !== $this->_class->rootEntityName) ? $this->_em->getClassMetadata($this->_class->rootEntityName) : $this->_class; + $rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name); + $rootTableName = $rootClass->getTableName(); + $rootTableStmt = $this->_conn->prepare($rootPersister->_getInsertSQL()); + + // Prepare statements for sub tables. + $subTableStmts = array(); + + if ($rootClass !== $this->_class) { + $subTableStmts[$this->_class->getTableName()] = $this->_conn->prepare($this->_getInsertSQL()); + } + + foreach ($this->_class->parentClasses as $parentClassName) { + $parentClass = $this->_em->getClassMetadata($parentClassName); + $parentTableName = $parentClass->getTableName(); + + if ($parentClass !== $rootClass) { + $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName); + $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL()); + } + } + + // Execute all inserts. For each entity: + // 1) Insert on root table + // 2) Insert on sub tables + foreach ($this->_queuedInserts as $entity) { + $insertData = $this->_prepareInsertData($entity); + + // Execute insert on root table + $paramIndex = 1; + + foreach ($insertData[$rootTableName] as $columnName => $value) { + $rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); + } + + $rootTableStmt->execute(); + + if ($isPostInsertId) { + $id = $idGen->generate($this->_em, $entity); + $postInsertIds[$id] = $entity; + } else { + $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); + } + + // Execute inserts on subtables. + // The order doesn't matter because all child tables link to the root table via FK. + foreach ($subTableStmts as $tableName => $stmt) { + $data = isset($insertData[$tableName]) ? $insertData[$tableName] : array(); + $paramIndex = 1; + + foreach ((array) $id as $idName => $idVal) { + $type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING; + + $stmt->bindValue($paramIndex++, $idVal, $type); + } + + foreach ($data as $columnName => $value) { + $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); + } + + $stmt->execute(); + } + } + + $rootTableStmt->closeCursor(); + + foreach ($subTableStmts as $stmt) { + $stmt->closeCursor(); + } + + if ($this->_class->isVersioned) { + $this->assignDefaultVersionValue($entity, $id); + } + + $this->_queuedInserts = array(); + + return $postInsertIds; + } + + /** + * {@inheritdoc} + */ + public function update($entity) + { + $updateData = $this->_prepareUpdateData($entity); + + if (($isVersioned = $this->_class->isVersioned) != false) { + $versionedClass = $this->_getVersionedClassMetadata(); + $versionedTable = $versionedClass->getTableName(); + } + + if ($updateData) { + foreach ($updateData as $tableName => $data) { + $this->_updateTable( + $entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName + ); + } + + // Make sure the table with the version column is updated even if no columns on that + // table were affected. + if ($isVersioned && ! isset($updateData[$versionedTable])) { + $this->_updateTable($entity, $this->quoteStrategy->getTableName($versionedClass, $this->_platform), array(), true); + + $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); + $this->assignDefaultVersionValue($entity, $id); + } + } + } + + /** + * {@inheritdoc} + */ + public function delete($entity) + { + $identifier = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); + $this->deleteJoinTableRecords($identifier); + + $id = array_combine($this->_class->getIdentifierColumnNames(), $identifier); + + // If the database platform supports FKs, just + // delete the row from the root table. Cascades do the rest. + if ($this->_platform->supportsForeignKeyConstraints()) { + $this->_conn->delete( + $this->quoteStrategy->getTableName($this->_em->getClassMetadata($this->_class->rootEntityName), $this->_platform), $id + ); + } else { + // Delete from all tables individually, starting from this class' table up to the root table. + $this->_conn->delete($this->quoteStrategy->getTableName($this->_class, $this->_platform), $id); + + foreach ($this->_class->parentClasses as $parentClass) { + $this->_conn->delete( + $this->quoteStrategy->getTableName($this->_em->getClassMetadata($parentClass), $this->_platform), $id + ); + } + } + } + + /** + * {@inheritdoc} + */ + protected function _getSelectEntitiesSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) + { + $idColumns = $this->_class->getIdentifierColumnNames(); + $baseTableAlias = $this->_getSQLTableAlias($this->_class->name); + + // Create the column list fragment only once + if ($this->_selectColumnListSql === null) { + + $this->_rsm = new ResultSetMapping(); + $this->_rsm->addEntityResult($this->_class->name, 'r'); + + // Add regular columns + $columnList = ''; + + foreach ($this->_class->fieldMappings as $fieldName => $mapping) { + if ($columnList != '') $columnList .= ', '; + + $columnList .= $this->_getSelectColumnSQL( + $fieldName, + isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class + ); + } + + // Add foreign key columns + foreach ($this->_class->associationMappings as $assoc2) { + if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) { + $tableAlias = isset($assoc2['inherited']) ? $this->_getSQLTableAlias($assoc2['inherited']) : $baseTableAlias; + + foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { + if ($columnList != '') $columnList .= ', '; + + $columnList .= $this->getSelectJoinColumnSQL( + $tableAlias, + $srcColumn, + isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name + ); + } + } + } + + // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#_processSQLResult). + $discrColumn = $this->_class->discriminatorColumn['name']; + $tableAlias = ($this->_class->rootEntityName == $this->_class->name) ? $baseTableAlias : $this->_getSQLTableAlias($this->_class->rootEntityName); + $columnList .= ', ' . $tableAlias . '.' . $discrColumn; + + $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); + + $this->_rsm->setDiscriminatorColumn('r', $resultColumnName); + $this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn); + } + + // INNER JOIN parent tables + $joinSql = ''; + + foreach ($this->_class->parentClasses as $parentClassName) { + $parentClass = $this->_em->getClassMetadata($parentClassName); + $tableAlias = $this->_getSQLTableAlias($parentClassName); + $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->_platform) . ' ' . $tableAlias . ' ON '; + $first = true; + + foreach ($idColumns as $idColumn) { + if ($first) $first = false; else $joinSql .= ' AND '; + + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; + } + } + + // OUTER JOIN sub tables + foreach ($this->_class->subClasses as $subClassName) { + $subClass = $this->_em->getClassMetadata($subClassName); + $tableAlias = $this->_getSQLTableAlias($subClassName); + + if ($this->_selectColumnListSql === null) { + // Add subclass columns + foreach ($subClass->fieldMappings as $fieldName => $mapping) { + if (isset($mapping['inherited'])) continue; + + $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); + } + + // Add join columns (foreign keys) + foreach ($subClass->associationMappings as $assoc2) { + if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE && ! isset($assoc2['inherited'])) { + foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { + if ($columnList != '') $columnList .= ', '; + + $columnList .= $this->getSelectJoinColumnSQL( + $tableAlias, + $srcColumn, + isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name + ); + } + } + } + } + + // Add LEFT JOIN + $joinSql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->_platform) . ' ' . $tableAlias . ' ON '; + $first = true; + + foreach ($idColumns as $idColumn) { + if ($first) $first = false; else $joinSql .= ' AND '; + + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; + } + } + + $joinSql .= ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : ''; + + $conditionSql = ($criteria instanceof Criteria) + ? $this->_getSelectConditionCriteriaSQL($criteria) + : $this->_getSelectConditionSQL($criteria, $assoc); + + // If the current class in the root entity, add the filters + if ($filterSql = $this->generateFilterConditionSQL($this->_em->getClassMetadata($this->_class->rootEntityName), $this->_getSQLTableAlias($this->_class->rootEntityName))) { + if ($conditionSql) { + $conditionSql .= ' AND '; + } + + $conditionSql .= $filterSql; + } + + $orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy; + $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : ''; + + if ($this->_selectColumnListSql === null) { + $this->_selectColumnListSql = $columnList; + } + + $lockSql = ''; + + if ($lockMode == LockMode::PESSIMISTIC_READ) { + $lockSql = ' ' . $this->_platform->getReadLockSql(); + } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { + $lockSql = ' ' . $this->_platform->getWriteLockSql(); + } + + return $this->_platform->modifyLimitQuery('SELECT ' . $this->_selectColumnListSql + . ' FROM ' . $this->quoteStrategy->getTableName($this->_class, $this->_platform) . ' ' . $baseTableAlias + . $joinSql + . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql, $limit, $offset) + . $lockSql; + } + + /** + * Get the FROM and optionally JOIN conditions to lock the entity managed by this persister. + * + * @return string + */ + public function getLockTablesSql() + { + $idColumns = $this->_class->getIdentifierColumnNames(); + $baseTableAlias = $this->_getSQLTableAlias($this->_class->name); + + // INNER JOIN parent tables + $joinSql = ''; + + foreach ($this->_class->parentClasses as $parentClassName) { + $parentClass = $this->_em->getClassMetadata($parentClassName); + $tableAlias = $this->_getSQLTableAlias($parentClassName); + $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->_platform) . ' ' . $tableAlias . ' ON '; + $first = true; + + foreach ($idColumns as $idColumn) { + if ($first) $first = false; else $joinSql .= ' AND '; + + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; + } + } + + return 'FROM ' .$this->quoteStrategy->getTableName($this->_class, $this->_platform) . ' ' . $baseTableAlias . $joinSql; + } + + /* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ + protected function _getSelectColumnListSQL() + { + throw new \BadMethodCallException("Illegal invocation of ".__METHOD__."."); + } + + /** {@inheritdoc} */ + protected function _getInsertColumnList() + { + // Identifier columns must always come first in the column list of subclasses. + $columns = $this->_class->parentClasses ? $this->_class->getIdentifierColumnNames() : array(); + + foreach ($this->_class->reflFields as $name => $field) { + if (isset($this->_class->fieldMappings[$name]['inherited']) && ! isset($this->_class->fieldMappings[$name]['id']) + || isset($this->_class->associationMappings[$name]['inherited']) + || ($this->_class->isVersioned && $this->_class->versionField == $name)) { + continue; + } + + if (isset($this->_class->associationMappings[$name])) { + $assoc = $this->_class->associationMappings[$name]; + if ($assoc['type'] & ClassMetadata::TO_ONE && $assoc['isOwningSide']) { + foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { + $columns[] = $sourceCol; + } + } + } else if ($this->_class->name != $this->_class->rootEntityName || + ! $this->_class->isIdGeneratorIdentity() || $this->_class->identifier[0] != $name) { + $columns[] = $this->quoteStrategy->getColumnName($name, $this->_class, $this->_platform); + } + } + + // Add discriminator column if it is the topmost class. + if ($this->_class->name == $this->_class->rootEntityName) { + $columns[] = $this->_class->discriminatorColumn['name']; + } + + return $columns; + } + + /** + * {@inheritdoc} + */ + protected function assignDefaultVersionValue($entity, $id) + { + $value = $this->fetchVersionValue($this->_getVersionedClassMetadata(), $id); + $this->_class->setFieldValue($entity, $this->_class->versionField, $value); + } + +}