/** * Creates a column definition as required by the DBAL from an ORM field mapping definition. * * @param ClassMetadata $class The class that owns the field mapping. * @param array $mapping The field mapping. * @param Table $table * @return array The portable column definition as required by the DBAL. */ private function _gatherColumn($class, array $mapping, $table) { $columnName = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); $columnType = $mapping['type']; $options = array(); $options['length'] = isset($mapping['length']) ? $mapping['length'] : null; $options['notnull'] = isset($mapping['nullable']) ? !$mapping['nullable'] : true; if ($class->isInheritanceTypeSingleTable() && count($class->parentClasses) > 0) { $options['notnull'] = false; } $options['platformOptions'] = array(); $options['platformOptions']['version'] = $class->isVersioned && $class->versionField == $mapping['fieldName'] ? true : false; if (strtolower($columnType) == 'string' && $options['length'] === null) { $options['length'] = 255; } if (isset($mapping['precision'])) { $options['precision'] = $mapping['precision']; } if (isset($mapping['scale'])) { $options['scale'] = $mapping['scale']; } if (isset($mapping['default'])) { $options['default'] = $mapping['default']; } if (isset($mapping['columnDefinition'])) { $options['columnDefinition'] = $mapping['columnDefinition']; } if (isset($mapping['options'])) { if (isset($mapping['options']['comment'])) { $options['comment'] = $mapping['options']['comment']; unset($mapping['options']['comment']); } if (isset($mapping['options']['unsigned'])) { $options['unsigned'] = $mapping['options']['unsigned']; unset($mapping['options']['unsigned']); } if (isset($mapping['options']['fixed'])) { $options['fixed'] = $mapping['options']['fixed']; unset($mapping['options']['fixed']); } $options['customSchemaOptions'] = $mapping['options']; } if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == array($mapping['fieldName'])) { $options['autoincrement'] = true; } if ($class->isInheritanceTypeJoined() && $class->name != $class->rootEntityName) { $options['autoincrement'] = false; } if ($table->hasColumn($columnName)) { // required in some inheritance scenarios $table->changeColumn($columnName, $options); } else { $table->addColumn($columnName, $columnType, $options); } $isUnique = isset($mapping['unique']) ? $mapping['unique'] : false; if ($isUnique) { $table->addUniqueIndex(array($columnName)); } }
public function getEntityHistory($className, $id) { if (!$this->metadataFactory->isAudited($className)) { throw new NotAuditedException($className); } /** @var ClassMetadataInfo|ClassMetadata $class */ $class = $this->em->getClassMetadata($className); $tableName = $this->config->getTableName($class); if (!is_array($id)) { $id = array($class->identifier[0] => $id); } $whereId = array(); foreach ($class->identifier as $idField) { if (isset($class->fieldMappings[$idField])) { $columnName = $class->fieldMappings[$idField]['columnName']; } else { if (isset($class->associationMappings[$idField])) { $columnName = $class->associationMappings[$idField]['joinColumns'][0]; } else { continue; } } $whereId[] = "{$columnName} = ?"; } $whereSQL = implode(' AND ', $whereId); $columnList = array($this->config->getRevisionFieldName()); $columnMap = array(); foreach ($class->fieldNames as $columnName => $field) { $type = Type::getType($class->fieldMappings[$field]['type']); $columnList[] = $type->convertToPHPValueSQL($this->quoteStrategy->getColumnName($field, $class, $this->platform), $this->platform) . ' AS ' . $this->platform->quoteSingleIdentifier($field); $columnMap[$field] = $this->platform->getSQLResultCasing($columnName); } foreach ($class->associationMappings as $assoc) { if (($assoc['type'] & ClassMetadata::TO_ONE) == 0 || !$assoc['isOwningSide']) { continue; } foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { $columnList[] = $sourceCol; $columnMap[$sourceCol] = $this->platform->getSQLResultCasing($sourceCol); } } $values = array_values($id); $query = "SELECT " . implode(', ', $columnList) . " FROM " . $tableName . " e WHERE " . $whereSQL . " ORDER BY e.rev DESC"; $stmt = $this->em->getConnection()->executeQuery($query, $values); $result = array(); while ($row = $stmt->fetch(Query::HYDRATE_ARRAY)) { $rev = $row[$this->config->getRevisionFieldName()]; unset($row[$this->config->getRevisionFieldName()]); $result[] = $this->createEntity($class->name, $columnMap, $row, $rev); } return $result; }
/** * Generates a new order by clause that works in the scope of a select query wrapping the original * * @param OrderByClause $orderByClause * @return array */ private function rebuildOrderByClauseForOuterScope(OrderByClause $orderByClause) { $dqlAliasToSqlTableAliasMap = $searchPatterns = $replacements = $dqlAliasToClassMap = $selectListAdditions = $orderByItems = []; // Generate DQL alias -> SQL table alias mapping foreach (array_keys($this->rsm->aliasMap) as $dqlAlias) { $dqlAliasToClassMap[$dqlAlias] = $class = $this->queryComponents[$dqlAlias]['metadata']; $dqlAliasToSqlTableAliasMap[$dqlAlias] = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); } // Pattern to find table path expressions in the order by clause $fieldSearchPattern = '/(?<![a-z0-9_])%s\\.%s(?![a-z0-9_])/i'; // Generate search patterns for each field's path expression in the order by clause foreach ($this->rsm->fieldMappings as $fieldAlias => $fieldName) { $dqlAliasForFieldAlias = $this->rsm->columnOwnerMap[$fieldAlias]; $class = $dqlAliasToClassMap[$dqlAliasForFieldAlias]; // If the field is from a joined child table, we won't be ordering // on it. if (!isset($class->fieldMappings[$fieldName])) { continue; } $fieldMapping = $class->fieldMappings[$fieldName]; // Get the proper column name as will appear in the select list $columnName = $this->quoteStrategy->getColumnName($fieldName, $dqlAliasToClassMap[$dqlAliasForFieldAlias], $this->em->getConnection()->getDatabasePlatform()); // Get the SQL table alias for the entity and field $sqlTableAliasForFieldAlias = $dqlAliasToSqlTableAliasMap[$dqlAliasForFieldAlias]; if (isset($fieldMapping['declared']) && $fieldMapping['declared'] !== $class->name) { // Field was declared in a parent class, so we need to get the proper SQL table alias // for the joined parent table. $otherClassMetadata = $this->em->getClassMetadata($fieldMapping['declared']); if (!$otherClassMetadata->isMappedSuperclass) { $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias); } } // Compose search/replace patterns $searchPatterns[] = sprintf($fieldSearchPattern, $sqlTableAliasForFieldAlias, $columnName); $replacements[] = $fieldAlias; } foreach ($orderByClause->orderByItems as $orderByItem) { // Walk order by item to get string representation of it $orderByItemString = $this->walkOrderByItem($orderByItem); // Replace path expressions in the order by clause with their column alias $orderByItemString = preg_replace($searchPatterns, $replacements, $orderByItemString); $orderByItems[] = $orderByItemString; } return $orderByItems; }
private function getUniqueAndRequiredFields(ClassMetadata $meta, $entity) { $fields = array(); foreach ($meta->getFieldNames() as $fieldName) { $mapping = $meta->getFieldMapping($fieldName); if (!empty($mapping['id']) && $meta->usesIdGenerator()) { // autogenerated id continue; } if (!empty($mapping['nullable']) && empty($mapping['unique'])) { // is nullable and is not unique continue; } $fields[$fieldName]['value'] = $meta->getFieldValue($entity, $fieldName); $fields[$fieldName]['quotedColumn'] = $this->quotes->getColumnName($fieldName, $meta, $this->platform); $fields[$fieldName]['type'] = Type::getType($mapping['type']); } return $fields; }
/** * Generates a new order by clause that works in the scope of a select query wrapping the original * * @param OrderByClause $orderByClause * @return array */ private function rebuildOrderByClauseForOuterScope(OrderByClause $orderByClause) { $dqlAliasToSqlTableAliasMap = $searchPatterns = $replacements = $dqlAliasToClassMap = $selectListAdditions = $orderByItems = []; // Generate DQL alias -> SQL table alias mapping foreach (array_keys($this->rsm->aliasMap) as $dqlAlias) { $dqlAliasToClassMap[$dqlAlias] = $class = $this->queryComponents[$dqlAlias]['metadata']; $dqlAliasToSqlTableAliasMap[$dqlAlias] = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); } // Pattern to find table path expressions in the order by clause $fieldSearchPattern = '/(?<![a-z0-9_])%s\\.%s(?![a-z0-9_])/i'; // Generate search patterns for each field's path expression in the order by clause foreach ($this->rsm->fieldMappings as $fieldAlias => $columnName) { $dqlAliasForFieldAlias = $this->rsm->columnOwnerMap[$fieldAlias]; $columnName = $this->quoteStrategy->getColumnName($columnName, $dqlAliasToClassMap[$dqlAliasForFieldAlias], $this->em->getConnection()->getDatabasePlatform()); $sqlTableAliasForFieldAlias = $dqlAliasToSqlTableAliasMap[$dqlAliasForFieldAlias]; $searchPatterns[] = sprintf($fieldSearchPattern, $sqlTableAliasForFieldAlias, $columnName); $replacements[] = $fieldAlias; } $complexAddedOrderByAliases = 0; foreach ($orderByClause->orderByItems as $orderByItem) { // Walk order by item to get string representation of it $orderByItemString = $this->walkOrderByItem($orderByItem); // Replace path expressions in the order by clause with their column alias $orderByItemString = preg_replace($searchPatterns, $replacements, $orderByItemString); // The order by items are not required to be in the select list on Oracle and PostgreSQL, but // for the sake of simplicity, order by items will be included in the select list on all platforms. // This doesn't impact functionality. $selectListAddition = trim(preg_replace('/([^ ]+) (?:asc|desc)/i', '$1', $orderByItemString)); // If the expression is an arithmetic expression, we need to create an alias for it. if ($orderByItem->expression instanceof ArithmeticTerm) { $orderByAlias = "ordr_" . $complexAddedOrderByAliases++; $orderByItemString = $orderByAlias . " " . $orderByItem->type; $selectListAddition .= " AS {$orderByAlias}"; } $selectListAdditions[] = $selectListAddition; $orderByItems[] = $orderByItemString; } return array($selectListAdditions, $orderByItems); }
/** * @param ClassMetadata $class * * @return string * @throws \Doctrine\DBAL\DBALException */ private function getInsertRevisionSQL($class) { if (!isset($this->insertRevisionSQL[$class->name])) { $placeholders = array('?', '?'); $tableName = $this->config->getTableName($class); $sql = "INSERT INTO " . $tableName . " (" . $this->config->getRevisionFieldName() . ", " . $this->config->getRevisionTypeFieldName(); $fields = array(); foreach ($class->associationMappings as $field => $assoc) { if ($class->isInheritanceTypeJoined() && $class->isInheritedAssociation($field)) { continue; } if (($assoc['type'] & ClassMetadata::TO_ONE) > 0 && $assoc['isOwningSide']) { foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { $fields[$sourceCol] = true; $sql .= ', ' . $sourceCol; $placeholders[] = '?'; } } } foreach ($class->fieldNames as $field) { if (array_key_exists($field, $fields)) { continue; } if ($class->isInheritanceTypeJoined() && $class->isInheritedField($field) && !$class->isIdentifier($field)) { continue; } $type = Type::getType($class->fieldMappings[$field]['type']); $placeholders[] = !empty($class->fieldMappings[$field]['requireSQLConversion']) ? $type->convertToDatabaseValueSQL('?', $this->platform) : '?'; $sql .= ', ' . $this->quoteStrategy->getColumnName($field, $class, $this->platform); } if ($class->isInheritanceTypeJoined() && $class->rootEntityName == $class->name || $class->isInheritanceTypeSingleTable()) { $sql .= ', ' . $class->discriminatorColumn['name']; $placeholders[] = '?'; } $sql .= ") VALUES (" . implode(", ", $placeholders) . ")"; $this->insertRevisionSQL[$class->name] = $sql; } return $this->insertRevisionSQL[$class->name]; }
/** * {@inheritdoc} */ public function walkCollectionMemberExpression($collMemberExpr) { $sql = $collMemberExpr->not ? 'NOT ' : ''; $sql .= 'EXISTS (SELECT 1 FROM '; $entityExpr = $collMemberExpr->entityExpression; $collPathExpr = $collMemberExpr->collectionValuedPathExpression; $fieldName = $collPathExpr->field; $dqlAlias = $collPathExpr->identificationVariable; $class = $this->queryComponents[$dqlAlias]['metadata']; switch (true) { // InputParameter case $entityExpr instanceof AST\InputParameter: $dqlParamKey = $entityExpr->name; $entitySql = '?'; break; // SingleValuedAssociationPathExpression | IdentificationVariable // SingleValuedAssociationPathExpression | IdentificationVariable case $entityExpr instanceof AST\PathExpression: $entitySql = $this->walkPathExpression($entityExpr); break; default: throw new \BadMethodCallException("Not implemented"); } $assoc = $class->associationMappings[$fieldName]; if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE '; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $sqlParts = array(); foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform); $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; } foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) { if (isset($dqlParamKey)) { $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); } $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; } $sql .= implode(' AND ', $sqlParts); } else { // many-to-many $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; $joinTable = $owningAssoc['joinTable']; // SQL table aliases $joinTableAlias = $this->getSQLTableAlias($joinTable['name']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); // join to target table $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias . ' INNER JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' ON '; // join conditions $joinColumns = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns']; $joinSqlParts = array(); foreach ($joinColumns as $joinColumn) { $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform); $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn; } $sql .= implode(' AND ', $joinSqlParts); $sql .= ' WHERE '; $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $sqlParts = array(); foreach ($joinColumns as $joinColumn) { $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform); $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn; } foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) { if (isset($dqlParamKey)) { $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); } $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; } $sql .= implode(' AND ', $sqlParts); } return $sql . ')'; }
/** * Builds the left-hand-side of a where condition statement. * * @param string $field * @param array|null $assoc * * @return string * * @throws \Doctrine\ORM\ORMException */ protected function getSelectConditionStatementColumnSQL($field, $assoc = null) { if (isset($this->class->columnNames[$field])) { $className = (isset($this->class->fieldMappings[$field]['inherited'])) ? $this->class->fieldMappings[$field]['inherited'] : $this->class->name; return $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform); } if (isset($this->class->associationMappings[$field])) { if ( ! $this->class->associationMappings[$field]['isOwningSide']) { throw ORMException::invalidFindByInverseAssociation($this->class->name, $field); } $joinColumn = $this->class->associationMappings[$field]['joinColumns'][0]; $className = (isset($this->class->associationMappings[$field]['inherited'])) ? $this->class->associationMappings[$field]['inherited'] : $this->class->name; return $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); } if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) { // very careless developers could potentially open up this normally hidden api for userland attacks, // therefore checking for spaces and function calls which are not allowed. // found a join column condition, not really a "field" return $field; } throw ORMException::unrecognizedField($field); }
/** * Builds the left-hand-side of a where condition statement. * * @param string $field * @param array|null $assoc * * @return string[] * * @throws \Doctrine\ORM\ORMException */ private function getSelectConditionStatementColumnSQL($field, $assoc = null) { if (isset($this->class->columnNames[$field])) { $className = isset($this->class->fieldMappings[$field]['inherited']) ? $this->class->fieldMappings[$field]['inherited'] : $this->class->name; return array($this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform)); } if (isset($this->class->associationMappings[$field])) { $association = $this->class->associationMappings[$field]; // Many-To-Many requires join table check for joinColumn $columns = array(); $class = $this->class; if ($association['type'] === ClassMetadata::MANY_TO_MANY) { if (!$association['isOwningSide']) { $association = $assoc; } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); $joinColumns = $assoc['isOwningSide'] ? $association['joinTable']['joinColumns'] : $association['joinTable']['inverseJoinColumns']; foreach ($joinColumns as $joinColumn) { $columns[] = $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } } else { if (!$association['isOwningSide']) { throw ORMException::invalidFindByInverseAssociation($this->class->name, $field); } $className = isset($association['inherited']) ? $association['inherited'] : $this->class->name; foreach ($association['joinColumns'] as $joinColumn) { $columns[] = $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); } } return $columns; } if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) { // very careless developers could potentially open up this normally hidden api for userland attacks, // therefore checking for spaces and function calls which are not allowed. // found a join column condition, not really a "field" return array($field); } throw ORMException::unrecognizedField($field); }
/** * @param DoctrineSqlFilter $sqlFilter * @param QuoteStrategy $quoteStrategy * @param ClassMetadata $targetEntity * @param string $targetTableAlias * @param string $targetEntityPropertyName * @return string * @throws InvalidQueryRewritingConstraintException * @throws \Exception */ protected function getSqlForSimpleProperty(DoctrineSqlFilter $sqlFilter, QuoteStrategy $quoteStrategy, ClassMetadata $targetEntity, $targetTableAlias, $targetEntityPropertyName) { $quotedColumnName = $quoteStrategy->getColumnName($targetEntityPropertyName, $targetEntity, $this->entityManager->getConnection()->getDatabasePlatform()); $propertyPointer = $targetTableAlias . '.' . $quotedColumnName; if (is_array($this->operandDefinition)) { foreach ($this->operandDefinition as $operandIterator => $singleOperandValue) { $this->setParameter($sqlFilter, $operandIterator, $singleOperandValue); } } else { $this->setParameter($sqlFilter, $this->operandDefinition, $this->operand); } return $this->getConstraintStringForSimpleProperty($sqlFilter, $propertyPointer); }
/** * Gets the SQL snippet of a qualified column name for the given field name. * * @param string $field The field name. * @param ClassMetadata $class The class that declares this field. The table this class is * mapped to must own the column for the given field. * @param string $alias * * @return string */ protected function getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { $root = $alias == 'r' ? '' : $alias; $tableAlias = $this->getSQLTableAlias($class->name, $root); $columnName = $this->quoteStrategy->getColumnName($field, $class, $this->platform); $sql = $tableAlias . '.' . $columnName; $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]); $this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field); if (isset($class->fieldMappings[$field]['requireSQLConversion'])) { $type = Type::getType($class->getTypeOfField($field)); $sql = $type->convertToPHPValueSQL($sql, $this->platform); } return $sql . ' AS ' . $columnAlias; }