File "AbstractSchemaManager-20250318150803.php"
Full Path: /home/pulsehostuk9/public_html/invoicer.pulsehost.co.uk/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager-20250318150803.php
File size: 23.69 KB
MIME-type: text/x-php
Charset: utf-8
<?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;
}
}