queryColumnName). * @psalm-param class-string $class * @psalm-param array $renamedColumns * @psalm-param self::COLUMN_RENAMING_*|null $renameMode */ public function addRootEntityFromClassMetadata( string $class, string $alias, array $renamedColumns = [], int|null $renameMode = null, ): void { $renameMode = $renameMode ?: $this->defaultRenameMode; $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); $this->addEntityResult($class, $alias); $this->addAllClassFields($class, $alias, $columnAliasMap); } /** * Adds a joined entity and all of its fields to the result set. * * @param string $class The class name of the joined entity. * @param string $alias The unique alias to use for the joined entity. * @param string $parentAlias The alias of the entity result that is the parent of this joined result. * @param string $relation The association field that connects the parent entity result * with the joined entity result. * @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). * @psalm-param class-string $class * @psalm-param array $renamedColumns * @psalm-param self::COLUMN_RENAMING_*|null $renameMode */ public function addJoinedEntityFromClassMetadata( string $class, string $alias, string $parentAlias, string $relation, array $renamedColumns = [], int|null $renameMode = null, ): void { $renameMode = $renameMode ?: $this->defaultRenameMode; $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation); $this->addAllClassFields($class, $alias, $columnAliasMap); } /** * Adds all fields of the given class to the result set mapping (columns and meta fields). * * @param string[] $columnAliasMap * @psalm-param array $columnAliasMap * * @throws InvalidArgumentException */ protected function addAllClassFields(string $class, string $alias, array $columnAliasMap = []): void { $classMetadata = $this->em->getClassMetadata($class); $platform = $this->em->getConnection()->getDatabasePlatform(); if (! $this->isInheritanceSupported($classMetadata)) { throw new InvalidArgumentException('ResultSetMapping builder does not currently support your inheritance scheme.'); } foreach ($classMetadata->getColumnNames() as $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $columnAlias = $this->getSQLResultCasing($platform, $columnAliasMap[$columnName]); if (isset($this->fieldMappings[$columnAlias])) { throw new InvalidArgumentException(sprintf( "The column '%s' conflicts with another column in the mapper.", $columnName, )); } $this->addFieldResult($alias, $columnAlias, $propertyName); $enumType = $classMetadata->getFieldMapping($propertyName)->enumType ?? null; if (! empty($enumType)) { $this->addEnumResult($columnAlias, $enumType); } } foreach ($classMetadata->associationMappings as $associationMapping) { if ($associationMapping->isToOneOwningSide()) { $targetClass = $this->em->getClassMetadata($associationMapping->targetEntity); $isIdentifier = isset($associationMapping->id) && $associationMapping->id === true; foreach ($associationMapping->joinColumns as $joinColumn) { $columnName = $joinColumn->name; $columnAlias = $this->getSQLResultCasing($platform, $columnAliasMap[$columnName]); $columnType = PersisterHelper::getTypeOfColumn($joinColumn->referencedColumnName, $targetClass, $this->em); if (isset($this->metaMappings[$columnAlias])) { throw new InvalidArgumentException(sprintf( "The column '%s' conflicts with another column in the mapper.", $columnAlias, )); } $this->addMetaResult($alias, $columnAlias, $columnName, $isIdentifier, $columnType); } } } } private function isInheritanceSupported(ClassMetadata $classMetadata): bool { if ( $classMetadata->isInheritanceTypeSingleTable() && in_array($classMetadata->name, $classMetadata->discriminatorMap, true) ) { return true; } return ! ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()); } /** * Gets column alias for a given column. * * @psalm-param array $customRenameColumns * * @psalm-assert self::COLUMN_RENAMING_* $mode */ private function getColumnAlias(string $columnName, int $mode, array $customRenameColumns): string { return match ($mode) { self::COLUMN_RENAMING_INCREMENT => $columnName . $this->sqlCounter++, self::COLUMN_RENAMING_CUSTOM => $customRenameColumns[$columnName] ?? $columnName, self::COLUMN_RENAMING_NONE => $columnName, default => throw new InvalidArgumentException(sprintf('%d is not a valid value for $mode', $mode)), }; } /** * Retrieves a class columns and join columns aliases that are used in the SELECT clause. * * This depends on the renaming mode selected by the user. * * @psalm-param class-string $className * @psalm-param self::COLUMN_RENAMING_* $mode * @psalm-param array $customRenameColumns * * @return string[] * @psalm-return array */ private function getColumnAliasMap( string $className, int $mode, array $customRenameColumns, ): array { if ($customRenameColumns) { // for BC with 2.2-2.3 API $mode = self::COLUMN_RENAMING_CUSTOM; } $columnAlias = []; $class = $this->em->getClassMetadata($className); foreach ($class->getColumnNames() as $columnName) { $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); } foreach ($class->associationMappings as $associationMapping) { if ($associationMapping->isToOneOwningSide()) { foreach ($associationMapping->joinColumns as $joinColumn) { $columnName = $joinColumn->name; $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); } } } return $columnAlias; } /** * Generates the Select clause from this ResultSetMappingBuilder. * * Works only for all the entity results. The select parts for scalar * expressions have to be written manually. * * @param string[] $tableAliases * @psalm-param array $tableAliases */ public function generateSelectClause(array $tableAliases = []): string { $sql = ''; foreach ($this->columnOwnerMap as $columnName => $dqlAlias) { $tableAlias = $tableAliases[$dqlAlias] ?? $dqlAlias; if ($sql !== '') { $sql .= ', '; } if (isset($this->fieldMappings[$columnName])) { $class = $this->em->getClassMetadata($this->declaringClasses[$columnName]); $fieldName = $this->fieldMappings[$columnName]; $classFieldMapping = $class->fieldMappings[$fieldName]; $columnSql = $tableAlias . '.' . $classFieldMapping->columnName; $type = Type::getType($classFieldMapping->type); $columnSql = $type->convertToPHPValueSQL($columnSql, $this->em->getConnection()->getDatabasePlatform()); $sql .= $columnSql; } elseif (isset($this->metaMappings[$columnName])) { $sql .= $tableAlias . '.' . $this->metaMappings[$columnName]; } elseif (isset($this->discriminatorColumns[$dqlAlias])) { $sql .= $tableAlias . '.' . $this->discriminatorColumns[$dqlAlias]; } $sql .= ' AS ' . $columnName; } return $sql; } public function __toString(): string { return $this->generateSelectClause([]); } }