Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
vendor
/
doctrine
/
dbal
/
src
/
Schema
:
AbstractSchemaManager-20250318150803.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php declare(strict_types=1); namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\DatabaseRequired; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\Exception\NotSupported; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\Exception\TableDoesNotExist; use function array_filter; use function array_intersect; use function array_map; use function array_values; use function count; use function strtolower; /** * Base class for schema managers. Schema managers are used to inspect and/or * modify the database schema/structure. * * @template T of AbstractPlatform */ abstract class AbstractSchemaManager { /** @param T $platform */ public function __construct(protected Connection $connection, protected AbstractPlatform $platform) { } /** * Lists the available databases for this connection. * * @return array<int, string> * * @throws Exception */ public function listDatabases(): array { return array_map(function (array $row): string { return $this->_getPortableDatabaseDefinition($row); }, $this->connection->fetchAllAssociative( $this->platform->getListDatabasesSQL(), )); } /** * Returns a list of the names of all schemata in the current database. * * @return list<string> * * @throws Exception */ public function listSchemaNames(): array { throw NotSupported::new(__METHOD__); } /** * Lists the available sequences for this connection. * * @return array<int, Sequence> * * @throws Exception */ public function listSequences(): array { return $this->filterAssetNames( array_map(function (array $row): Sequence { return $this->_getPortableSequenceDefinition($row); }, $this->connection->fetchAllAssociative( $this->platform->getListSequencesSQL( $this->getDatabase(__METHOD__), ), )), ); } /** * Lists the columns for a given table. * * In contrast to other libraries and to the old version of Doctrine, * this column definition does try to contain the 'primary' column for * the reason that it is not portable across different RDBMS. Use * {@see listTableIndexes($tableName)} to retrieve the primary key * of a table. Where a RDBMS specifies more details, these are held * in the platformDetails array. * * @return array<string, Column> * * @throws Exception */ public function listTableColumns(string $table): array { $database = $this->getDatabase(__METHOD__); return $this->_getPortableTableColumnList( $table, $database, $this->selectTableColumns($database, $this->normalizeName($table)) ->fetchAllAssociative(), ); } /** * Lists the indexes for a given table returning an array of Index instances. * * Keys of the portable indexes list are all lower-cased. * * @return array<string, Index> * * @throws Exception */ public function listTableIndexes(string $table): array { $database = $this->getDatabase(__METHOD__); $table = $this->normalizeName($table); return $this->_getPortableTableIndexesList( $this->selectIndexColumns( $database, $table, )->fetchAllAssociative(), $table, ); } /** * Returns true if all the given tables exist. * * @param array<int, string> $names * * @throws Exception */ public function tablesExist(array $names): bool { $names = array_map('strtolower', $names); return count($names) === count(array_intersect($names, array_map('strtolower', $this->listTableNames()))); } public function tableExists(string $tableName): bool { return $this->tablesExist([$tableName]); } /** * Returns a list of all tables in the current database. * * @return array<int, string> * * @throws Exception */ public function listTableNames(): array { return $this->filterAssetNames( array_map(function (array $row): string { return $this->_getPortableTableDefinition($row); }, $this->selectTableNames( $this->getDatabase(__METHOD__), )->fetchAllAssociative()), ); } /** * Filters asset names if they are configured to return only a subset of all * the found elements. * * @param array<int, mixed> $assetNames * * @return array<int, mixed> */ private function filterAssetNames(array $assetNames): array { $filter = $this->connection->getConfiguration()->getSchemaAssetsFilter(); return array_values(array_filter($assetNames, $filter)); } /** * Lists the tables for this connection. * * @return list<Table> * * @throws Exception */ public function listTables(): array { $database = $this->getDatabase(__METHOD__); $tableColumnsByTable = $this->fetchTableColumnsByTable($database); $indexColumnsByTable = $this->fetchIndexColumnsByTable($database); $foreignKeyColumnsByTable = $this->fetchForeignKeyColumnsByTable($database); $tableOptionsByTable = $this->fetchTableOptionsByTable($database); $filter = $this->connection->getConfiguration()->getSchemaAssetsFilter(); $tables = []; foreach ($tableColumnsByTable as $tableName => $tableColumns) { if (! $filter($tableName)) { continue; } $tables[] = new Table( $tableName, $this->_getPortableTableColumnList($tableName, $database, $tableColumns), $this->_getPortableTableIndexesList($indexColumnsByTable[$tableName] ?? [], $tableName), [], $this->_getPortableTableForeignKeysList($foreignKeyColumnsByTable[$tableName] ?? []), $tableOptionsByTable[$tableName] ?? [], ); } return $tables; } /** * An extension point for those platforms where case sensitivity of the object name depends on whether it's quoted. * * Such platforms should convert a possibly quoted name into a value of the corresponding case. */ protected function normalizeName(string $name): string { $identifier = new Identifier($name); return $identifier->getName(); } /** * Selects names of tables in the specified database. * * @throws Exception */ abstract protected function selectTableNames(string $databaseName): Result; /** * Selects definitions of table columns in the specified database. If the table name is specified, narrows down * the selection to this table. * * @throws Exception */ abstract protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result; /** * Selects definitions of index columns in the specified database. If the table name is specified, narrows down * the selection to this table. * * @throws Exception */ abstract protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result; /** * Selects definitions of foreign key columns in the specified database. If the table name is specified, * narrows down the selection to this table. * * @throws Exception */ abstract protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result; /** * Fetches definitions of table columns in the specified database and returns them grouped by table name. * * @return array<string,list<array<string,mixed>>> * * @throws Exception */ protected function fetchTableColumnsByTable(string $databaseName): array { return $this->fetchAllAssociativeGrouped($this->selectTableColumns($databaseName)); } /** * Fetches definitions of index columns in the specified database and returns them grouped by table name. * * @return array<string,list<array<string,mixed>>> * * @throws Exception */ protected function fetchIndexColumnsByTable(string $databaseName): array { return $this->fetchAllAssociativeGrouped($this->selectIndexColumns($databaseName)); } /** * Fetches definitions of foreign key columns in the specified database and returns them grouped by table name. * * @return array<string, list<array<string, mixed>>> * * @throws Exception */ protected function fetchForeignKeyColumnsByTable(string $databaseName): array { return $this->fetchAllAssociativeGrouped( $this->selectForeignKeyColumns($databaseName), ); } /** * Fetches table options for the tables in the specified database and returns them grouped by table name. * If the table name is specified, narrows down the selection to this table. * * @return array<string,array<string,mixed>> * * @throws Exception */ abstract protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array; /** * Introspects the table with the given name. * * @throws Exception */ public function introspectTable(string $name): Table { $columns = $this->listTableColumns($name); if ($columns === []) { throw TableDoesNotExist::new($name); } return new Table( $name, $columns, $this->listTableIndexes($name), [], $this->listTableForeignKeys($name), $this->getTableOptions($name), ); } /** * Lists the views this connection has. * * @return list<View> * * @throws Exception */ public function listViews(): array { return array_map(function (array $row): View { return $this->_getPortableViewDefinition($row); }, $this->connection->fetchAllAssociative( $this->platform->getListViewsSQL( $this->getDatabase(__METHOD__), ), )); } /** * Lists the foreign keys for the given table. * * @return array<int|string, ForeignKeyConstraint> * * @throws Exception */ public function listTableForeignKeys(string $table): array { $database = $this->getDatabase(__METHOD__); return $this->_getPortableTableForeignKeysList( $this->selectForeignKeyColumns( $database, $this->normalizeName($table), )->fetchAllAssociative(), ); } /** * @return array<string, mixed> * * @throws Exception */ private function getTableOptions(string $name): array { $normalizedName = $this->normalizeName($name); return $this->fetchTableOptionsByTable( $this->getDatabase(__METHOD__), $normalizedName, )[$normalizedName] ?? []; } /* drop*() Methods */ /** * Drops a database. * * NOTE: You can not drop the database this SchemaManager is currently connected to. * * @throws Exception */ public function dropDatabase(string $database): void { $this->connection->executeStatement( $this->platform->getDropDatabaseSQL($database), ); } /** * Drops a schema. * * @throws Exception */ public function dropSchema(string $schemaName): void { $this->connection->executeStatement( $this->platform->getDropSchemaSQL($schemaName), ); } /** * Drops the given table. * * @throws Exception */ public function dropTable(string $name): void { $this->connection->executeStatement( $this->platform->getDropTableSQL($name), ); } /** * Drops the index from the given table. * * @throws Exception */ public function dropIndex(string $index, string $table): void { $this->connection->executeStatement( $this->platform->getDropIndexSQL($index, $table), ); } /** * Drops a foreign key from a table. * * @throws Exception */ public function dropForeignKey(string $name, string $table): void { $this->connection->executeStatement( $this->platform->getDropForeignKeySQL($name, $table), ); } /** * Drops a sequence with a given name. * * @throws Exception */ public function dropSequence(string $name): void { $this->connection->executeStatement( $this->platform->getDropSequenceSQL($name), ); } /** * Drops the unique constraint from the given table. * * @throws Exception */ public function dropUniqueConstraint(string $name, string $tableName): void { $this->connection->executeStatement( $this->platform->getDropUniqueConstraintSQL($name, $tableName), ); } /** * Drops a view. * * @throws Exception */ public function dropView(string $name): void { $this->connection->executeStatement( $this->platform->getDropViewSQL($name), ); } /* create*() Methods */ /** @throws Exception */ public function createSchemaObjects(Schema $schema): void { $this->executeStatements($schema->toSql($this->platform)); } /** * Creates a new database. * * @throws Exception */ public function createDatabase(string $database): void { $this->connection->executeStatement( $this->platform->getCreateDatabaseSQL($database), ); } /** * Creates a new table. * * @throws Exception */ public function createTable(Table $table): void { $this->executeStatements($this->platform->getCreateTableSQL($table)); } /** * Creates a new sequence. * * @throws Exception */ public function createSequence(Sequence $sequence): void { $this->connection->executeStatement( $this->platform->getCreateSequenceSQL($sequence), ); } /** * Creates a new index on a table. * * @param string $table The name of the table on which the index is to be created. * * @throws Exception */ public function createIndex(Index $index, string $table): void { $this->connection->executeStatement( $this->platform->getCreateIndexSQL($index, $table), ); } /** * Creates a new foreign key. * * @param ForeignKeyConstraint $foreignKey The ForeignKey instance. * @param string $table The name of the table on which the foreign key is to be created. * * @throws Exception */ public function createForeignKey(ForeignKeyConstraint $foreignKey, string $table): void { $this->connection->executeStatement( $this->platform->getCreateForeignKeySQL($foreignKey, $table), ); } /** * Creates a unique constraint on a table. * * @throws Exception */ public function createUniqueConstraint(UniqueConstraint $uniqueConstraint, string $tableName): void { $this->connection->executeStatement( $this->platform->getCreateUniqueConstraintSQL($uniqueConstraint, $tableName), ); } /** * Creates a new view. * * @throws Exception */ public function createView(View $view): void { $this->connection->executeStatement( $this->platform->getCreateViewSQL( $view->getQuotedName($this->platform), $view->getSql(), ), ); } /** @throws Exception */ public function dropSchemaObjects(Schema $schema): void { $this->executeStatements($schema->toDropSql($this->platform)); } /** * Alters an existing schema. * * @throws Exception */ public function alterSchema(SchemaDiff $schemaDiff): void { $this->executeStatements($this->platform->getAlterSchemaSQL($schemaDiff)); } /** * Migrates an existing schema to a new schema. * * @throws Exception */ public function migrateSchema(Schema $newSchema): void { $schemaDiff = $this->createComparator() ->compareSchemas($this->introspectSchema(), $newSchema); $this->alterSchema($schemaDiff); } /* alterTable() Methods */ /** * Alters an existing tables schema. * * @throws Exception */ public function alterTable(TableDiff $tableDiff): void { $this->executeStatements($this->platform->getAlterTableSQL($tableDiff)); } /** * Renames a given table to another name. * * @throws Exception */ public function renameTable(string $name, string $newName): void { $this->connection->executeStatement( $this->platform->getRenameTableSQL($name, $newName), ); } /** * Methods for filtering return values of list*() methods to convert * the native DBMS data definition to a portable Doctrine definition */ /** @param array<string, string> $database */ protected function _getPortableDatabaseDefinition(array $database): string { throw NotSupported::new(__METHOD__); } /** @param array<string, mixed> $sequence */ protected function _getPortableSequenceDefinition(array $sequence): Sequence { throw NotSupported::new(__METHOD__); } /** * Independent of the database the keys of the column list result are lowercased. * * The name of the created column instance however is kept in its case. * * @param array<int, array<string, mixed>> $tableColumns * * @return array<string, Column> * * @throws Exception */ protected function _getPortableTableColumnList(string $table, string $database, array $tableColumns): array { $list = []; foreach ($tableColumns as $tableColumn) { $column = $this->_getPortableTableColumnDefinition($tableColumn); $name = strtolower($column->getQuotedName($this->platform)); $list[$name] = $column; } return $list; } /** * Gets Table Column Definition. * * @param array<string, mixed> $tableColumn * * @throws Exception */ abstract protected function _getPortableTableColumnDefinition(array $tableColumn): Column; /** * Aggregates and groups the index results according to the required data result. * * @param array<int, array<string, mixed>> $tableIndexes * * @return array<string, Index> * * @throws Exception */ protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array { $result = []; foreach ($tableIndexes as $tableIndex) { $indexName = $keyName = $tableIndex['key_name']; if ($tableIndex['primary']) { $keyName = 'primary'; } $keyName = strtolower($keyName); if (! isset($result[$keyName])) { $options = [ 'lengths' => [], ]; if (isset($tableIndex['where'])) { $options['where'] = $tableIndex['where']; } $result[$keyName] = [ 'name' => $indexName, 'columns' => [], 'unique' => ! $tableIndex['non_unique'], 'primary' => $tableIndex['primary'], 'flags' => $tableIndex['flags'] ?? [], 'options' => $options, ]; } $result[$keyName]['columns'][] = $tableIndex['column_name']; $result[$keyName]['options']['lengths'][] = $tableIndex['length'] ?? null; } $indexes = []; foreach ($result as $indexKey => $data) { $indexes[$indexKey] = new Index( $data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options'], ); } return $indexes; } /** @param array<string, string> $table */ abstract protected function _getPortableTableDefinition(array $table): string; /** @param array<string, mixed> $view */ abstract protected function _getPortableViewDefinition(array $view): View; /** * @param array<int|string, array<string, mixed>> $tableForeignKeys * * @return array<int, ForeignKeyConstraint> */ protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array { $list = []; foreach ($tableForeignKeys as $value) { $list[] = $this->_getPortableTableForeignKeyDefinition($value); } return $list; } /** @param array<string, mixed> $tableForeignKey */ abstract protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint; /** * @param array<int, string> $sql * * @throws Exception */ private function executeStatements(array $sql): void { foreach ($sql as $query) { $this->connection->executeStatement($query); } } /** * Returns a {@see Schema} instance representing the current database schema. * * @throws Exception */ public function introspectSchema(): Schema { $schemaNames = []; if ($this->platform->supportsSchemas()) { $schemaNames = $this->listSchemaNames(); } $sequences = []; if ($this->platform->supportsSequences()) { $sequences = $this->listSequences(); } $tables = $this->listTables(); return new Schema($tables, $sequences, $this->createSchemaConfig(), $schemaNames); } /** * Creates the configuration for this schema. * * @throws Exception */ public function createSchemaConfig(): SchemaConfig { $schemaConfig = new SchemaConfig(); $schemaConfig->setMaxIdentifierLength($this->platform->getMaxIdentifierLength()); $params = $this->connection->getParams(); if (! isset($params['defaultTableOptions'])) { $params['defaultTableOptions'] = []; } if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) { $params['defaultTableOptions']['charset'] = $params['charset']; } $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']); return $schemaConfig; } /** @throws Exception */ private function getDatabase(string $methodName): string { $database = $this->connection->getDatabase(); if ($database === null) { throw DatabaseRequired::new($methodName); } return $database; } public function createComparator(): Comparator { return new Comparator($this->platform); } /** * @return array<string,list<array<string,mixed>>> * * @throws Exception */ private function fetchAllAssociativeGrouped(Result $result): array { $data = []; foreach ($result->fetchAllAssociative() as $row) { $tableName = $this->_getPortableTableDefinition($row); $data[$tableName][] = $row; } return $data; } }