View source
<?php
namespace Drupal\Core\Command;
use Drupal\Component\Utility\Variable;
use Drupal\Core\Database\Connection;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class DbDumpCommand extends DbCommandBase {
protected $excludeTables = [
'test[0-9]+',
];
protected function configure() {
$this
->setName('dump-database-d8-mysql')
->setDescription('Dump the current database to a generation script')
->addOption('schema-only', NULL, InputOption::VALUE_OPTIONAL, 'A comma separated list of tables to only export the schema without data.', 'cache.*,sessions,watchdog');
parent::configure();
}
protected function execute(InputInterface $input, OutputInterface $output) : int {
$connection = $this
->getDatabaseConnection($input);
if ($input
->hasParameterOption([
'--ansi',
]) !== TRUE) {
$output
->setDecorated(FALSE);
}
$schema_tables = $input
->getOption('schema-only');
$schema_tables = explode(',', $schema_tables);
$output
->writeln($this
->generateScript($connection, $schema_tables), OutputInterface::OUTPUT_RAW);
return 0;
}
protected function generateScript(Connection $connection, array $schema_only = []) {
$tables = '';
$schema_only_patterns = [];
foreach ($schema_only as $match) {
$schema_only_patterns[] = '/^' . $match . '$/';
}
foreach ($this
->getTables($connection) as $table) {
$schema = $this
->getTableSchema($connection, $table);
if (empty($schema_only_patterns) || preg_replace($schema_only_patterns, '', $table)) {
$data = $this
->getTableData($connection, $table);
}
else {
$data = [];
}
$tables .= $this
->getTableScript($table, $schema, $data);
}
$script = $this
->getTemplate();
$script = str_replace('{{VERSION}}', \Drupal::VERSION, $script);
$script = str_replace('{{TABLES}}', trim($tables), $script);
return trim($script);
}
protected function getTables(Connection $connection) {
$tables = array_values($connection
->schema()
->findTables('%'));
foreach ($tables as $key => $table) {
foreach ($this->excludeTables as $pattern) {
if (preg_match('/^' . $pattern . '$/', $table)) {
unset($tables[$key]);
}
}
}
asort($tables);
return $tables;
}
protected function getTableSchema(Connection $connection, $table) {
if ($connection
->databaseType() !== 'mysql') {
throw new \RuntimeException('This script can only be used with MySQL database backends.');
}
$query = $connection
->query("SHOW FULL COLUMNS FROM {" . $table . "}");
$definition = [];
while (($row = $query
->fetchAssoc()) !== FALSE) {
$name = $row['Field'];
preg_match('@([a-z]+)(?:\\((\\d+)(?:,(\\d+))?\\))?\\s*(unsigned)?@', $row['Type'], $matches);
$type = $this
->fieldTypeMap($connection, $matches[1]);
if ($row['Extra'] === 'auto_increment') {
$type = 'serial';
}
$definition['fields'][$name] = [
'type' => $type,
'not null' => $row['Null'] === 'NO',
];
if ($size = $this
->fieldSizeMap($connection, $matches[1])) {
$definition['fields'][$name]['size'] = $size;
}
if (isset($matches[2]) && $type === 'numeric') {
$definition['fields'][$name]['precision'] = $matches[2];
$definition['fields'][$name]['scale'] = $matches[3];
}
elseif ($type === 'time') {
$definition['fields'][$name]['type'] = 'varchar';
$definition['fields'][$name]['length'] = '100';
}
elseif ($type === 'datetime') {
$definition['fields'][$name]['mysql_type'] = 'datetime';
$definition['fields'][$name]['pgsql_type'] = 'timestamp without time zone';
$definition['fields'][$name]['sqlite_type'] = 'varchar';
$definition['fields'][$name]['sqlsrv_type'] = 'smalldatetime';
}
elseif (!isset($definition['fields'][$name]['size'])) {
$definition['fields'][$name]['length'] = $matches[2] ?? 100;
}
if (isset($row['Default'])) {
$definition['fields'][$name]['default'] = $row['Default'];
}
if (isset($matches[4])) {
$definition['fields'][$name]['unsigned'] = TRUE;
}
if (isset($row['Collation']) && $row['Collation'] == 'ascii_bin') {
$definition['fields'][$name]['type'] = 'varchar_ascii';
$definition['fields'][$name]['binary'] = TRUE;
}
if (isset($row['Collation']) && $row['Collation'] == 'ascii_general_ci') {
$definition['fields'][$name]['type'] = 'varchar_ascii';
}
if (isset($row['Collation']) && $row['Collation'] == 'utf8_bin') {
$definition['fields'][$name]['binary'] = TRUE;
}
}
$this
->getTableIndexes($connection, $table, $definition);
$this
->getTableCollation($connection, $table, $definition);
return $definition;
}
protected function getTableIndexes(Connection $connection, $table, &$definition) {
$query = $connection
->query("SHOW INDEX FROM {" . $table . "}");
while (($row = $query
->fetchAssoc()) !== FALSE) {
$index_name = $row['Key_name'];
$column = $row['Column_name'];
$order = $row['Seq_in_index'] - 1;
if ($row['Sub_part']) {
$column = [
$column,
$row['Sub_part'],
];
}
if ($index_name === 'PRIMARY') {
$definition['primary key'][$order] = $column;
}
elseif ($row['Non_unique'] == 0) {
$definition['unique keys'][$index_name][$order] = $column;
}
else {
$definition['indexes'][$index_name][$order] = $column;
}
}
}
protected function getTableCollation(Connection $connection, $table, &$definition) {
$table = trim($connection
->prefixTables('{' . $table . '}'), '"');
$query = $connection
->query("SHOW TABLE STATUS WHERE NAME = :table_name", [
':table_name' => $table,
]);
$data = $query
->fetchAssoc();
[
$charset,
] = explode('_', $data['Collation'], 2);
$definition['mysql_character_set'] = $charset;
}
protected function getTableData(Connection $connection, $table) {
$order = $this
->getFieldOrder($connection, $table);
$query = $connection
->query("SELECT * FROM {" . $table . "} " . $order);
$results = [];
while (($row = $query
->fetchAssoc()) !== FALSE) {
$results[] = $row;
}
return $results;
}
protected function fieldTypeMap(Connection $connection, $type) {
$map = array_map('strtolower', $connection
->schema()
->getFieldTypeMap());
$map = array_flip($map);
return isset($map[$type]) ? explode(':', $map[$type])[0] : $type;
}
protected function fieldSizeMap(Connection $connection, $type) {
$map = array_map('strtolower', $connection
->schema()
->getFieldTypeMap());
$map = array_flip($map);
if (!isset($map[$type])) {
return NULL;
}
$schema_type = explode(':', $map[$type])[0];
if (in_array($schema_type, [
'blob',
'float',
'int',
'text',
])) {
return explode(':', $map[$type])[1];
}
}
protected function getFieldOrder(Connection $connection, $table) {
$connection_info = $connection
->getConnectionOptions();
$order = '';
$query = "SELECT `COLUMN_NAME` FROM `information_schema`.`COLUMNS`\n WHERE (`TABLE_SCHEMA` = '" . $connection_info['database'] . "')\n AND (`TABLE_NAME` = '{" . $table . "}') AND (`COLUMN_KEY` = 'PRI')\n ORDER BY COLUMN_NAME";
$results = $connection
->query($query);
while (($row = $results
->fetchAssoc()) !== FALSE) {
$order .= $row['COLUMN_NAME'] . ', ';
}
if (!empty($order)) {
$order = ' ORDER BY ' . rtrim($order, ', ');
}
return $order;
}
protected function getTemplate() {
$script = <<<'ENDOFSCRIPT'
<?php
// phpcs:ignoreFile
/**
* @file
* A database agnostic dump for testing purposes.
*
* This file was generated by the Drupal {{VERSION}} db-tools.php script.
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
// Ensure any tables with a serial column with a value of 0 are created as
// expected.
if ($connection->databaseType() === 'mysql') {
$sql_mode = $connection->query("SELECT @@sql_mode;")->fetchField();
$connection->query("SET sql_mode = '$sql_mode,NO_AUTO_VALUE_ON_ZERO'");
}
{{TABLES}}
// Reset the SQL mode.
if ($connection->databaseType() === 'mysql') {
$connection->query("SET sql_mode = '$sql_mode'");
}
ENDOFSCRIPT;
return $script;
}
protected function getTableScript($table, array $schema, array $data) {
$output = '';
$output .= "\$connection->schema()->createTable('" . $table . "', " . Variable::export($schema) . ");\n\n";
if (!empty($data)) {
$insert = '';
foreach ($data as $record) {
$insert .= "->values(" . Variable::export($record) . ")\n";
}
$fields = Variable::export(array_keys($schema['fields']));
$output .= <<<EOT
\$connection->insert('{<span class="php-variable">$table</span>}')
->fields({<span class="php-variable">$fields</span>})
{<span class="php-variable">$insert</span>}->execute();
EOT;
}
return $output;
}
}