Issue #2908886 by andypost, martin107, kim.pepper, daffie, aleevas, vacho, catch, longwave, alexpott, voleger, dww: Split off schema management out of schema.inc

merge-requests/593/head
catch 2021-04-22 16:00:44 +01:00
parent aea84a7cc9
commit 535fb843aa
10 changed files with 193 additions and 25 deletions

View File

@ -524,7 +524,7 @@ services:
class: Drupal\Core\Extension\ModuleInstaller
tags:
- { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator }
arguments: ['%app.root%', '@module_handler', '@kernel']
arguments: ['%app.root%', '@module_handler', '@kernel', '@database']
lazy: true
extension.list.module:
class: Drupal\Core\Extension\ModuleExtensionList

View File

@ -112,8 +112,15 @@ function drupal_set_installed_schema_version($module, $version) {
*
* @param string $module
* The module for which the tables will be created.
*
* @deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct
* replacement is provided.
*
* @see https://www.drupal.org/node/2970993
* @see \Drupal\Core\Extension\ModuleInstaller::installSchema()
*/
function drupal_install_schema($module) {
@trigger_error('drupal_install_schema() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct replacement is provided. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
$schema = drupal_get_module_schema($module);
_drupal_schema_initialize($schema, $module, FALSE);
@ -127,8 +134,15 @@ function drupal_install_schema($module) {
*
* @param string $module
* The module for which the tables will be removed.
*
* @deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct
* replacement is provided.
*
* @see https://www.drupal.org/node/2970993
* @see \Drupal\Core\Extension\ModuleInstaller::uninstallSchema()
*/
function drupal_uninstall_schema($module) {
@trigger_error('drupal_uninstall_schema() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct replacement is provided. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
$tables = drupal_get_module_schema($module);
_drupal_schema_initialize($tables, $module, FALSE);
$schema = \Drupal::database()->schema();
@ -151,8 +165,16 @@ function drupal_uninstall_schema($module) {
* @param string $table
* The name of the table. If not given, the module's complete schema
* is returned.
*
* @deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct
* replacement is provided. Testing classes could use
* \Drupal\TestTools\Extension\SchemaInspector for introspection.
*
* @see https://www.drupal.org/node/2970993
* @see \Drupal\TestTools\Extension\SchemaInspector::getTablesSpecification()
*/
function drupal_get_module_schema($module, $table = NULL) {
@trigger_error('drupal_get_module_schema() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct replacement is provided. Testing classes could use \Drupal\TestTools\Extension\SchemaInspector for introspection. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
// Load the .install file to get hook_schema.
module_load_install($module);
$schema = \Drupal::moduleHandler()->invoke($module, 'schema');
@ -181,8 +203,14 @@ function drupal_get_module_schema($module, $table = NULL) {
* (optional) Whether to additionally remove 'description' keys of all tables
* and fields to improve performance of serialize() and unserialize().
* Defaults to TRUE.
*
* @deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct
* replacement is provided.
*
* @see https://www.drupal.org/node/2970993
*/
function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRUE) {
@trigger_error('_drupal_schema_initialize() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct replacement is provided. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
// Set the name and module key for all tables.
foreach ($schema as $name => &$table) {
if (empty($table['module'])) {

View File

@ -390,7 +390,9 @@ use Drupal\Core\Database\Query\SelectInterface;
* ];
* @endcode
*
* @see drupal_install_schema()
* @see \Drupal\Core\Extension\ModuleInstaller::installSchema()
* @see \Drupal\Core\Extension\ModuleInstaller::uninstallSchema()
* @see \Drupal\TestTools\Extension\SchemaInspector::getTablesSpecification()
*
* @}
*/

View File

@ -4,6 +4,7 @@ namespace Drupal\Core\Extension;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\DrupalKernelInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\FieldableEntityInterface;
@ -44,6 +45,13 @@ class ModuleInstaller implements ModuleInstallerInterface {
*/
protected $root;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* The uninstall validators.
*
@ -60,14 +68,21 @@ class ModuleInstaller implements ModuleInstallerInterface {
* The module handler.
* @param \Drupal\Core\DrupalKernelInterface $kernel
* The drupal kernel.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*
* @see \Drupal\Core\DrupalKernel
* @see \Drupal\Core\CoreServiceProvider
*/
public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel) {
public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel, Connection $connection = NULL) {
$this->root = $root;
$this->moduleHandler = $module_handler;
$this->kernel = $kernel;
if (!$connection) {
@trigger_error('The database connection must be passed to ' . __METHOD__ . '(). Creating ' . __CLASS__ . ' without it is deprecated in drupal:9.2.0 and will be required in drupal:10.0.0. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
$connection = \Drupal::service('database');
}
$this->connection = $connection;
}
/**
@ -227,7 +242,7 @@ class ModuleInstaller implements ModuleInstallerInterface {
$this->moduleHandler->invokeAll('module_preinstall', [$module]);
// Now install the module's schema if necessary.
drupal_install_schema($module);
$this->installSchema($module);
// Clear plugin manager caches.
\Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
@ -468,7 +483,7 @@ class ModuleInstaller implements ModuleInstallerInterface {
}
// Remove the schema.
drupal_uninstall_schema($module);
$this->uninstallSchema($module);
// Remove the module's entry from the config. Don't check schema when
// uninstalling a module since we are only clearing a key.
@ -585,6 +600,7 @@ class ModuleInstaller implements ModuleInstallerInterface {
// dependencies.
$container = $this->kernel->getContainer();
$this->moduleHandler = $container->get('module_handler');
$this->connection = $container->get('database');
}
/**
@ -606,4 +622,38 @@ class ModuleInstaller implements ModuleInstallerInterface {
return $reasons;
}
/**
* Creates all tables defined in a module's hook_schema().
*
* @param string $module
* The module for which the tables will be created.
*
* @internal
*/
protected function installSchema(string $module): void {
$tables = $this->moduleHandler->invoke($module, 'schema') ?? [];
$schema = $this->connection->schema();
foreach ($tables as $name => $table) {
$schema->createTable($name, $table);
}
}
/**
* Removes all tables defined in a module's hook_schema().
*
* @param string $module
* The module for which the tables will be removed.
*
* @internal
*/
protected function uninstallSchema(string $module): void {
$tables = $this->moduleHandler->invoke($module, 'schema') ?? [];
$schema = $this->connection->schema();
foreach (array_keys($tables) as $table) {
if ($schema->tableExists($table)) {
$schema->dropTable($table);
}
}
}
}

View File

@ -30,7 +30,7 @@ function module_test_schema() {
* Implements hook_install().
*/
function module_test_install() {
$schema = drupal_get_module_schema('module_test', 'module_test');
$schema = module_test_schema()['module_test'];
Database::getConnection()->insert('module_test')
->fields([
'data' => $schema['fields']['data']['type'],

View File

@ -8,6 +8,7 @@ use Drupal\Core\Database\Database;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Tests\BrowserTestBase;
use Drupal\TestTools\Extension\SchemaInspector;
/**
* Helper class for module test cases.
@ -40,7 +41,7 @@ abstract class ModuleTestBase extends BrowserTestBase {
* The name of the module.
*/
public function assertModuleTablesExist($module) {
$tables = array_keys(drupal_get_module_schema($module));
$tables = array_keys(SchemaInspector::getTablesSpecification(\Drupal::moduleHandler(), $module));
$tables_exist = TRUE;
$schema = Database::getConnection()->schema();
foreach ($tables as $table) {
@ -58,7 +59,7 @@ abstract class ModuleTestBase extends BrowserTestBase {
* The name of the module.
*/
public function assertModuleTablesDoNotExist($module) {
$tables = array_keys(drupal_get_module_schema($module));
$tables = array_keys(SchemaInspector::getTablesSpecification(\Drupal::moduleHandler(), $module));
$tables_exist = FALSE;
$schema = Database::getConnection()->schema();
foreach ($tables as $table) {

View File

@ -13,15 +13,14 @@ class InsertDefaultsTest extends DatabaseTestBase {
/**
* Tests that we can run a query that uses default values for everything.
*
* @see \database_test_schema()
*/
public function testDefaultInsert() {
$query = $this->connection->insert('test')->useDefaults(['job']);
$id = $query->execute();
$schema = drupal_get_module_schema('database_test', 'test');
$job = $this->connection->query('SELECT [job] FROM {test} WHERE [id] = :id', [':id' => $id])->fetchField();
$this->assertEqual($schema['fields']['job']['default'], $job, 'Default field value is set.');
$this->assertSame('Undefined', $job, 'Default field value is set.');
}
/**
@ -45,17 +44,16 @@ class InsertDefaultsTest extends DatabaseTestBase {
/**
* Tests that we can insert fields with values and defaults in the same query.
*
* @see \database_test_schema()
*/
public function testDefaultInsertWithFields() {
$query = $this->connection->insert('test')
->fields(['name' => 'Bob'])
->useDefaults(['job']);
$id = $query->execute();
$schema = drupal_get_module_schema('database_test', 'test');
$job = $this->connection->query('SELECT [job] FROM {test} WHERE [id] = :id', [':id' => $id])->fetchField();
$this->assertEqual($schema['fields']['job']['default'], $job, 'Default field value is set.');
$this->assertSame('Undefined', $job, 'Default field value is set.');
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Drupal\KernelTests\Core\Extension;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests deprecated schema.inc functions.
*
* @group legacy
* @group extension
*/
class SchemaDeprecationTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['system', 'dblog'];
/**
* Tests deprecation of database schema API functions.
*/
public function testDeprecatedInstallSchema() {
$this->expectDeprecation('drupal_install_schema() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct replacement is provided. See https://www.drupal.org/node/2970993');
drupal_install_schema('dblog');
$table = 'watchdog';
$this->expectDeprecation('drupal_get_module_schema() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct replacement is provided. Testing classes could use \Drupal\TestTools\Extension\SchemaInspector for introspection. See https://www.drupal.org/node/2970993');
$this->assertArrayHasKey($table, drupal_get_module_schema('dblog'));
$this->assertTrue(\Drupal::database()->schema()->tableExists($table));
$this->expectDeprecation('drupal_uninstall_schema() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct replacement is provided. See https://www.drupal.org/node/2970993');
drupal_uninstall_schema('dblog');
}
/**
* Tests deprecation of _drupal_schema_initialize() function.
*/
public function testDeprecatedSchemaInitialize() {
$module = 'dblog';
$table = 'watchdog';
$schema = [$table => []];
$this->expectDeprecation('_drupal_schema_initialize() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct replacement is provided. See https://www.drupal.org/node/2970993');
_drupal_schema_initialize($schema, $module, FALSE);
$this->assertEquals($module, $schema[$table]['module']);
$this->assertEquals($table, $schema[$table]['name']);
}
}

View File

@ -21,6 +21,7 @@ use Drupal\Tests\PhpUnitCompatibilityTrait;
use Drupal\Tests\TestRequirementsTrait;
use Drupal\Tests\Traits\PhpUnitWarnings;
use Drupal\TestTools\Comparator\MarkupInterfaceComparator;
use Drupal\TestTools\Extension\SchemaInspector;
use Drupal\TestTools\TestVarDumper;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\TestCase;
@ -715,14 +716,20 @@ abstract class KernelTestBase extends TestCase implements ServiceProviderInterfa
* If $module is not enabled or the table schema cannot be found.
*/
protected function installSchema($module, $tables) {
// drupal_get_module_schema() is technically able to install a schema
// of a non-enabled module, but its ability to load the module's .install
// file depends on many other factors. To prevent differences in test
// behavior and non-reproducible test failures, we only allow the schema of
// explicitly loaded/enabled modules to be installed.
if (!$this->container->get('module_handler')->moduleExists($module)) {
/** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
$module_handler = $this->container->get('module_handler');
// Database connection schema is technically able to create database tables
// using any valid specification, for example of a non-enabled module. But
// ability to load the module's .install file depends on many other factors.
// To prevent differences in test behavior and non-reproducible test
// failures, we only allow the schema of explicitly loaded/enabled modules
// to be installed.
if (!$module_handler->moduleExists($module)) {
throw new \LogicException("$module module is not enabled.");
}
$specification = SchemaInspector::getTablesSpecification($module_handler, $module);
/** @var \Drupal\Core\Database\Schema $schema */
$schema = $this->container->get('database')->schema();
$tables = (array) $tables;
foreach ($tables as $table) {
// The tables key_value and key_value_expire are lazy loaded and therefore
@ -732,11 +739,10 @@ abstract class KernelTestBase extends TestCase implements ServiceProviderInterfa
@trigger_error('Installing the tables key_value and key_value_expire with the method KernelTestBase::installSchema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. The tables are now lazy loaded and therefore will be installed automatically when used. See https://www.drupal.org/node/3143286', E_USER_DEPRECATED);
continue;
}
$schema = drupal_get_module_schema($module, $table);
if (empty($schema)) {
if (empty($specification[$table])) {
throw new \LogicException("$module module does not define a schema for table '$table'.");
}
$this->container->get('database')->schema()->createTable($table, $schema);
$schema->createTable($table, $specification[$table]);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Drupal\TestTools\Extension;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Provides methods to access modules' schema.
*/
class SchemaInspector {
/**
* Returns the module's schema specification.
*
* This function can be used to retrieve a schema specification provided by
* hook_schema(), so it allows you to derive your tables from existing
* specifications.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $handler
* The module handler to use for calling schema hook.
* @param string $module
* The module to which the table belongs.
*
* @return array
* An array of schema definition provided by hook_schema().
*
* @see \hook_schema()
*/
public static function getTablesSpecification(ModuleHandlerInterface $handler, string $module): array {
if ($handler->loadInclude($module, 'install')) {
return $handler->invoke($module, 'schema') ?? [];
}
return [];
}
}