/** * Gathers the SQL for properly setting up the relations of the given class. * This includes the SQL for foreign key constraints and join tables. * * @param ClassMetadata $class * @param Table $table * @param Schema $schema * @param array $addedFks * @param array $blacklistedFks * * @return void * * @throws \Doctrine\ORM\ORMException */ private function gatherRelationsSql($class, $table, $schema, &$addedFks, &$blacklistedFks) { foreach ($class->associationMappings as $mapping) { if (isset($mapping['inherited'])) { continue; } $foreignClass = $this->em->getClassMetadata($mapping['targetEntity']); if ($mapping['type'] & ClassMetadata::TO_ONE && $mapping['isOwningSide']) { $primaryKeyColumns = array(); // PK is unnecessary for this relation-type $this->gatherRelationJoinColumns($mapping['joinColumns'], $table, $foreignClass, $mapping, $primaryKeyColumns, $addedFks, $blacklistedFks); } elseif ($mapping['type'] == ClassMetadata::ONE_TO_MANY && $mapping['isOwningSide']) { //... create join table, one-many through join table supported later throw ORMException::notSupported(); } elseif ($mapping['type'] == ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) { // create join table $joinTable = $mapping['joinTable']; $theJoinTable = $schema->createTable($this->quoteStrategy->getJoinTableName($mapping, $foreignClass, $this->platform)); $primaryKeyColumns = array(); // Build first FK constraint (relation table => source table) $this->gatherRelationJoinColumns($joinTable['joinColumns'], $theJoinTable, $class, $mapping, $primaryKeyColumns, $addedFks, $blacklistedFks); // Build second FK constraint (relation table => target table) $this->gatherRelationJoinColumns($joinTable['inverseJoinColumns'], $theJoinTable, $foreignClass, $mapping, $primaryKeyColumns, $addedFks, $blacklistedFks); $theJoinTable->setPrimaryKey($primaryKeyColumns); } } }
/** * {@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 . ')'; }
/** * Gets the SQL join fragment used when selecting entities from a * many-to-many association. * * @param array $manyToMany * * @return string */ protected function getSelectManyToManyJoinSQL(array $manyToMany) { $conditions = array(); $association = $manyToMany; $sourceTableAlias = $this->getSQLTableAlias($this->class->name); if ( ! $manyToMany['isOwningSide']) { $targetEntity = $this->em->getClassMetadata($manyToMany['targetEntity']); $association = $targetEntity->associationMappings[$manyToMany['mappedBy']]; } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); $joinColumns = ($manyToMany['isOwningSide']) ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; foreach ($joinColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableName . '.' . $quotedSourceColumn; } return ' INNER JOIN ' . $joinTableName . ' ON ' . implode(' AND ', $conditions); }
/** * 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 array $assoc * @param object $sourceEntity * @param int|null $offset * @param int|null $limit * * @return \Doctrine\DBAL\Driver\Statement * * @throws \Doctrine\ORM\Mapping\MappingException */ private function getManyToManyStatement(array $assoc, $sourceEntity, $offset = null, $limit = null) { $this->switchPersisterContext($offset, $limit); $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); $class = $sourceClass; $association = $assoc; $criteria = array(); $parameters = array(); if (!$assoc['isOwningSide']) { $class = $this->em->getClassMetadata($assoc['targetEntity']); $association = $class->associationMappings[$assoc['mappedBy']]; } $joinColumns = $assoc['isOwningSide'] ? $association['joinTable']['joinColumns'] : $association['joinTable']['inverseJoinColumns']; $quotedJoinTable = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); foreach ($joinColumns as $joinColumn) { $sourceKeyColumn = $joinColumn['referencedColumnName']; $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); switch (true) { case $sourceClass->containsForeignIdentifier: $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); if (isset($sourceClass->associationMappings[$field])) { $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } break; case isset($sourceClass->fieldNames[$sourceKeyColumn]): $field = $sourceClass->fieldNames[$sourceKeyColumn]; $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); break; default: throw MappingException::joinColumnMustPointToMappedField($sourceClass->name, $sourceKeyColumn); } $criteria[$quotedJoinTable . '.' . $quotedKeyColumn] = $value; $parameters[] = array('value' => $value, 'field' => $field, 'class' => $sourceClass); } $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); list($params, $types) = $this->expandToManyParameters($parameters); return $this->conn->executeQuery($sql, $params, $types); }