Commit 553ed06a authored by catch's avatar catch
Browse files

Issue #1784312 by chx, katbailey, msonnabaum, Fabianx: Stop doing so much pre-kernel bootstrapping.

parent 8fcd5546
......@@ -2264,6 +2264,13 @@ function _drupal_bootstrap_configuration() {
// Load the procedural configuration system helper functions.
require_once DRUPAL_ROOT . '/core/includes/config.inc';
// Redirect the user to the installation script if Drupal has not been
// installed yet (i.e., if no $databases array has been defined in the
// settings.php file) and we are not already installing.
if (empty($GLOBALS['databases']) && !drupal_installation_attempted()) {
include_once DRUPAL_ROOT . '/core/includes/install.inc';
install_goto('core/install.php');
}
}
/**
......@@ -2359,15 +2366,6 @@ function _drupal_initialize_db_test_prefix() {
* Initializes the database system by loading database.inc.
*/
function _drupal_bootstrap_database() {
// Redirect the user to the installation script if Drupal has not been
// installed yet (i.e., if no $databases array has been defined in the
// settings.php file) and we are not already installing.
if (empty($GLOBALS['databases']) && !drupal_installation_attempted()) {
include_once DRUPAL_ROOT . '/core/includes/install.inc';
install_goto('core/install.php');
}
// Initialize the database system. Note that the connection
// won't be initialized until it is actually requested.
require_once DRUPAL_ROOT . '/core/includes/database.inc';
......@@ -2442,10 +2440,9 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
$container = $new_container;
}
if (!isset($container)) {
// Return a ContainerBuilder instance with the bare essentials needed for any
// full bootstrap regardless of whether there will be a DrupalKernel involved.
// This will get merged with the full Kernel-built Container on normal page
// requests.
// This is only ever used by the installer and by run-tests.sh.
// @todo Remove this entire section once these have been converted to use a
// kernel.
$container = new ContainerBuilder();
// Register active configuration storage.
......@@ -2707,11 +2704,9 @@ function language($type, $reset = FALSE) {
// When the language_manager service exists (is both defined and the 'request'
// scope is active in the container), use it to get the language. Otherwise
// return the default language.
try {
if (drupal_container()->isScopeActive('request')) {
$language_manager = drupal_container()->get('language_manager', Container::NULL_ON_INVALID_REFERENCE);
}
catch (DependencyInjectionRuntimeException $e) {
}
if (isset($language_manager)) {
return $language_manager->getLanguage($type);
......@@ -3448,54 +3443,6 @@ function drupal_check_memory_limit($required, $memory_limit = NULL) {
return ((!$memory_limit) || ($memory_limit == -1) || (parse_size($memory_limit) >= parse_size($required)));
}
/**
* Instantiates and statically caches a storage controller for generated PHP code.
*
* By default, this returns an instance of the
* Drupal\Component\PhpStorage\MTimeProtectedFileStorage class.
*
* Classes implementing
* Drupal\Component\PhpStorage\PhpStorageInterface can be registered for a
* specific bin or as a default implementation.
*
* @param $name
* The name for which the storage controller should be returned. Defaults to
* 'default'. The name is also used as the storage bin if one is not
* specified in the configuration.
*
* @return Drupal\Component\PhpStorage\PhpStorageInterface
* An instantiated storage controller for the specified name.
*
* @see Drupal\Component\PhpStorage\PhpStorageInterface
*/
function drupal_php_storage($name = 'default') {
global $conf;
$storage_controllers = &drupal_static(__FUNCTION__);
if (!isset($storage_controllers[$name])) {
if (isset($conf['php_storage'][$name])) {
$configuration = $conf['php_storage'][$name];
}
elseif (isset($conf['php_storage']['default'])) {
$configuration = $conf['php_storage']['default'];
}
else {
$configuration = array(
'class' => 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage',
'secret' => drupal_get_hash_salt(),
);
}
$class = isset($configuration['class']) ? $configuration['class'] : 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage';
if (!isset($configuration['bin'])) {
$configuration['bin'] = $name;
}
if (!isset($configuration['directory'])) {
$configuration['directory'] = DRUPAL_ROOT . '/' . variable_get('file_public_path', conf_path() . '/files') . '/php';
}
$storage_controllers[$name] = new $class($configuration);
}
return $storage_controllers[$name];
}
/**
* @defgroup lock Locking mechanisms
* @{
......
......@@ -3,6 +3,7 @@
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\Component\PhpStorage\PhpStorageFactory;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Datetime\DrupalDateTime;
......@@ -6745,8 +6746,8 @@ function drupal_flush_all_caches() {
menu_router_rebuild();
// Wipe the PHP Storage caches.
drupal_php_storage('service_container')->deleteAll();
drupal_php_storage('twig')->deleteAll();
PhpStorageFactory::get('service_container')->deleteAll();
PhpStorageFactory::get('twig')->deleteAll();
// Re-initialize the maintenance theme, if the current request attempted to
// use it. Unlike regular usages of this function, the installer and update
......
......@@ -1502,7 +1502,7 @@ function install_bootstrap_full(&$install_state) {
module_list_reset();
// Instantiate the kernel.
$kernel = new DrupalKernel('prod', FALSE, drupal_classloader());
$kernel = new DrupalKernel('prod', FALSE, drupal_classloader(), FALSE);
$kernel->boot();
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
}
......
<?php
/**
* @file
* Definition of Drupal\Component\PhpStorage\PhpStorageFactory.
*/
namespace Drupal\Component\PhpStorage;
/**
* Creates a php storage object
*/
class PhpStorageFactory {
/**
* Instantiates a storage controller for generated PHP code.
*
* By default, this returns an instance of the
* \Drupal\Component\PhpStorage\MTimeProtectedFileStorage class.
*
* Classes implementing
* \Drupal\Component\PhpStorage\PhpStorageInterface can be registered for a
* specific bin or as a default implementation.
*
* @param string $name
* The name for which the storage controller should be returned. Defaults to
* 'default'. The name is also used as the storage bin if one is not
* specified in the configuration.
*
* @return \Drupal\Component\PhpStorage\PhpStorageInterface
* An instantiated storage controller for the specified name.
*/
static function get($name) {
global $conf;
if (isset($conf['php_storage'][$name])) {
$configuration = $conf['php_storage'][$name];
}
elseif (isset($conf['php_storage']['default'])) {
$configuration = $conf['php_storage']['default'];
}
else {
$configuration = array(
'class' => 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage',
'secret' => $GLOBALS['drupal_hash_salt'],
);
}
$class = isset($configuration['class']) ? $configuration['class'] : 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage';
if (!isset($configuration['bin'])) {
$configuration['bin'] = $name;
}
if (!isset($configuration['directory'])) {
$path = isset($conf['file_public_path']) ? $conf['file_public_path'] : conf_path() . '/files';
$configuration['directory'] = DRUPAL_ROOT . "/$path/php";
}
return new $class($configuration);
}
}
<?php
/**
* @file
* Contains Drupal\Core\Config\BootstrapConfigStorageFactory.
*/
namespace Drupal\Core\Config;
/**
* Defines a factory for retrieving the config storage used pre-kernel.
*/
class BootstrapConfigStorageFactory {
/**
* Returns a configuration storage implementation.
*
* @return \Drupal\Core\Config\StorageInterface
* A configuration storage implementation.
*/
public static function get() {
if (isset($GLOBALS['conf']['drupal_bootstrap_config_storage'])) {
return call_user_func($GLOBALS['conf']['drupal_bootstrap_config_storage']);
}
else {
return new FileStorage(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
}
}
}
......@@ -11,7 +11,6 @@
use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterSerializationClassesPass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
......@@ -19,8 +18,6 @@
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Drupal\Core\Database\Database;
/**
* Bundle class for mandatory core services.
*
......@@ -28,11 +25,62 @@
* Injection Container. Modules wishing to register services to the container
* should extend Symfony's Bundle class directly, not this class.
*/
class CoreBundle extends Bundle
{
class CoreBundle extends Bundle {
/**
* Implements \Symfony\Component\HttpKernel\Bundle\BundleInterface::build().
*/
public function build(ContainerBuilder $container) {
// Register active configuration storage.
$container
->register('config.cachedstorage.storage', 'Drupal\Core\Config\FileStorage')
->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
// @todo Replace this with a cache.factory service plus 'config' argument.
$container
->register('cache.config', 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryClass('Drupal\Core\Cache\CacheFactory')
->setFactoryMethod('get')
->addArgument('config');
$container
->register('config.storage', 'Drupal\Core\Config\CachedStorage')
->addArgument(new Reference('config.cachedstorage.storage'))
->addArgument(new Reference('cache.config'));
// Register configuration object factory.
$container->register('config.subscriber.globalconf', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber');
$container->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher')
->addMethodCall('addSubscriber', array(new Reference('config.subscriber.globalconf')));
$container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
->addArgument(new Reference('config.storage'))
->addArgument(new Reference('dispatcher'));
// Register staging configuration storage.
$container
->register('config.storage.staging', 'Drupal\Core\Config\FileStorage')
->addArgument(config_get_config_directory(CONFIG_STAGING_DIRECTORY));
// Register the service for the default database connection.
$container->register('database', 'Drupal\Core\Database\Connection')
->setFactoryClass('Drupal\Core\Database\Database')
->setFactoryMethod('getConnection')
->addArgument('default');
// Register the KeyValueStore factory.
$container
->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory')
->addArgument(new Reference('service_container'));
$container
->register('keyvalue.database', 'Drupal\Core\KeyValueStore\KeyValueDatabaseFactory')
->addArgument(new Reference('database'));
$container->register('path.alias_manager', 'Drupal\Core\Path\AliasManager')
->addArgument(new Reference('database'))
->addArgument(new Reference('keyvalue.database'));
// Register the EntityManager.
$container->register('plugin.manager.entity', 'Drupal\Core\Entity\EntityManager');
// The 'request' scope and service enable services to depend on the Request
// object and get reconstructed when the request object changes (e.g.,
// during a subrequest).
......
......@@ -7,16 +7,15 @@
namespace Drupal\Core;
use Drupal\Core\Cache\CacheBackendInterface;
use Symfony\Component\ClassLoader\UniversalClassLoader;
use Drupal\Core\Config\FileStorage;
use Drupal\Component\PhpStorage\PhpStorageFactory;
use Drupal\Core\Config\BootstrapConfigStorageFactory;
use Drupal\Core\CoreBundle;
use Drupal\Component\PhpStorage\PhpStorageInterface;
use Symfony\Component\HttpKernel\Kernel;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Symfony\Component\ClassLoader\UniversalClassLoader;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\HttpKernel\Kernel;
/**
* The DrupalKernel class is the core of Drupal itself.
......@@ -58,6 +57,20 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface {
*/
protected $moduleData = array();
/**
* Holds a list of enabled modules and their paths.
*
* This is used to store module data as a container parameter so that it can
* be retrieved for registering namespaces when using a compiled container.
* When not using a compiled container, the namespaces get registered during
* the process of building the container.
*
* @var array
* An associative array whose keys are module names and whose values are
* module paths.
*/
protected $modulePaths = array();
/**
* PHP code storage object to use for the compiled container.
*
......@@ -72,6 +85,13 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface {
*/
protected $classLoader;
/**
* Config storage object used for reading enabled modules configuration.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $configStorage;
/**
* The list of the classnames of the bundles in this kernel.
*
......@@ -79,6 +99,20 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface {
*/
protected $bundleClasses;
/**
* Whether the container can be dumped.
*
* @var bool
*/
protected $allowDumping;
/**
* Whether the container needs to be dumped once booting is complete.
*
* @var bool
*/
protected $containerNeedsDumping;
/**
* Constructs a DrupalKernel object.
*
......@@ -95,15 +129,14 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface {
* the load from storage fails and a container rebuild is required. In
* this case, the loaded modules will be registered with this loader in
* order to be able to find the module bundles.
* @param \Drupal\Component\PhpStorage\PhpStorageInterface $storage
* (optional) An object handling the load and save of the compiled
* container. If not specified, the container will neither be stored to
* disk nor read from there.
* @param bool $allow_dumping
* (optional) FALSE to stop the container from being written to or read
* from disk. Defaults to TRUE.
*/
public function __construct($environment, $debug, UniversalClassLoader $class_loader, PhpStorageInterface $storage = NULL) {
public function __construct($environment, $debug, UniversalClassLoader $class_loader, $allow_dumping = TRUE) {
parent::__construct($environment, $debug);
$this->storage = $storage;
$this->classLoader = $class_loader;
$this->allowDumping = $allow_dumping;
}
/**
......@@ -125,28 +158,47 @@ public function boot() {
}
$this->initializeContainer();
$this->booted = TRUE;
// @todo Remove this once everything in the bootstrap has been converted to
// services in the DIC.
drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
// Now that full bootstrap is complete, we can dump the container if it
// was just rebuilt.
if ($this->containerNeedsDumping && !$this->dumpDrupalContainer($this->container, $this->getContainerBaseClass())) {
watchdog('DrupalKernel', 'Container cannot be written to disk');
}
}
/**
* Returns an array of available bundles.
*
* @return array
* The available bundles.
*/
public function registerBundles() {
$this->configStorage = BootstrapConfigStorageFactory::get();
$bundles = array(
new CoreBundle(),
);
if (!isset($this->moduleList)) {
$storage = new FileStorage(config_get_config_directory());
$module_list = $storage->read('system.module');
$module_list = $this->configStorage->read('system.module');
$this->moduleList = isset($module_list['enabled']) ? $module_list['enabled'] : array();
}
$namespaces = $this->classLoader->getNamespaces();
// We will need to store module locations in a container parameter so that
// we can register all namespaces when using a compiled container.
$this->modulePaths = array();
foreach ($this->moduleList as $module => $weight) {
// When installing new modules, the modules in the list passed to
// updateModules() do not yet have their namespace registered.
$namespace = 'Drupal\\' . $module;
if (!isset($namespaces[$namespace]) && $this->moduleData($module)) {
$this->classLoader->registerNamespace($namespace, dirname(DRUPAL_ROOT . '/' . $this->moduleData($module)->uri) . '/lib');
$path = dirname(DRUPAL_ROOT . '/' . $this->moduleData($module)->uri) . '/lib';
$this->modulePaths[$module] = $path;
$this->classLoader->registerNamespace($namespace, $path);
}
else {
$this->modulePaths[$module] = $namespaces[$namespace];
}
$camelized = ContainerBuilder::camelize($module);
$class = "Drupal\\{$module}\\{$camelized}Bundle";
......@@ -155,6 +207,13 @@ public function registerBundles() {
$this->bundleClasses[] = $class;
}
}
// Add site specific or test bundles.
if (!empty($GLOBALS['conf']['container_bundles'])) {
foreach ($GLOBALS['conf']['container_bundles'] as $class) {
$bundles[] = new $class();
$this->bundleClasses[] = $class;
}
}
return $bundles;
}
......@@ -177,10 +236,9 @@ protected function moduleData($module) {
$profiles_scanner = new SystemListing();
$all_profiles = $profiles_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
$profiles = array_keys(array_intersect_key($this->moduleList, $all_profiles));
$storage = new FileStorage(config_get_config_directory());
// If a module is within a profile directory but specifies another
// profile for testing, it needs to be found in the parent profile.
if (($parent_profile_config = $storage->read('simpletest.settings')) && isset($parent_profile_config['parent_profile']) && $parent_profile_config['parent_profile'] != $profiles[0]) {
if (($parent_profile_config = $this->configStorage->read('simpletest.settings')) && isset($parent_profile_config['parent_profile']) && $parent_profile_config['parent_profile'] != $profiles[0]) {
// In case both profile directories contain the same extension, the
// actual profile always has precedence.
array_unshift($profiles, $parent_profile_config['parent_profile']);
......@@ -195,7 +253,7 @@ protected function moduleData($module) {
/**
* Implements Drupal\Core\DrupalKernelInterface::updateModules().
*/
public function updateModules(array $module_list, array $module_paths = array(), ContainerBuilder $base_container = NULL) {
public function updateModules(array $module_list, array $module_paths = array()) {
$this->newModuleList = $module_list;
foreach ($module_paths as $module => $path) {
$this->moduleData[$module] = (object) array('uri' => $path);
......@@ -204,7 +262,6 @@ public function updateModules(array $module_list, array $module_paths = array(),
// list will take effect when boot() is called. If we have already booted,
// then reboot in order to refresh the bundle list and container.
if ($this->booted) {
drupal_container($base_container, TRUE);
$this->booted = FALSE;
$this->boot();
}
......@@ -236,10 +293,10 @@ protected function initializeContainer() {
$class = $this->getClassName();
$cache_file = $class . '.php';
if ($this->storage) {
if ($this->allowDumping) {
// First, try to load.
if (!class_exists($class, FALSE)) {
$this->storage->load($cache_file);
$this->storage()->load($cache_file);
}
// If the load succeeded or the class already existed, use it.
if (class_exists($class, FALSE)) {
......@@ -259,28 +316,33 @@ protected function initializeContainer() {
// web frontend or during the installer -- changed the list of enabled
// modules.
if (isset($this->container)) {
// All namespaces must be registered before we attempt to use any service
// from the container.
$namespaces = $this->classLoader->getNamespaces();
foreach ($this->container->getParameter('container.modules') as $module => $path) {
$namespace = 'Drupal\\' . $module;
if (!isset($namespaces[$namespace])) {
$this->classLoader->registerNamespace($namespace, $path);
}
}
$module_list = $this->moduleList ?: $this->container->get('config.factory')->get('system.module')->load()->get('enabled');
if (array_keys((array)$module_list) !== $this->container->getParameter('container.modules')) {
if (array_keys((array)$module_list) !== array_keys($this->container->getParameter('container.modules'))) {
unset($this->container);
}
}
if (!isset($this->container)) {
$this->container = $this->buildContainer();
if ($this->storage && !$this->dumpDrupalContainer($this->container, $this->getContainerBaseClass())) {
// We want to log this as an error but we cannot call watchdog() until
// the container has been fully built and set in drupal_container().
$error = 'Container cannot be written to disk';
if ($this->allowDumping) {
$this->containerNeedsDumping = TRUE;
}
}
$this->container->set('kernel', $this);
// Set the class loader which was registered as a synthetic service.
$this->container->set('class_loader', $this->classLoader);
drupal_container($this->container);
if (isset($error)) {
watchdog('DrupalKernel', $error);
}
}
/**
......@@ -292,12 +354,9 @@ protected function buildContainer() {
$this->initializeBundles();
$container = $this->getContainerBuilder();
$container->setParameter('container.bundles', $this->bundleClasses);
$container->setParameter('container.modules', array_keys($this->moduleList));
// Merge in the minimal bootstrap container.
if ($bootstrap_container = drupal_container()) {
$container->merge($bootstrap_container);
}
$container->setParameter('container.modules', $this->modulePaths);
// Register the class loader as a synthetic service.
$container->register('class_loader', 'Symfony\Component\ClassLoader\UniversalClassLoader')->setSynthetic(TRUE);
foreach ($this->bundles as $bundle) {
$bundle->build($container);
}
......@@ -329,14 +388,14 @@ protected function getContainerBuilder() {
* TRUE if the container was successfully dumped to disk.
*/
protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass) {
if (!$this->storage->writeable()) {
if (!$this->storage()->writeable()) {
return FALSE;
}
// Cache the container.
$dumper = new PhpDumper($container);
$class = $this->getClassName();
$content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass));
return $this->storage->save($class . '.php', $content);
return $this->storage()->save($class . '.php', $content);
}
/**
......@@ -352,4 +411,16 @@ protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass)
public function registerContainerConfiguration(LoaderInterface $loader) {
}
/**
* Gets the PHP code storage object to use for the compiled container.
*
* @return \Drupal\Component\PhpStorage\PhpStorageInterface
*/
protected function storage() {
if (!isset($this->storage)) {
$this->storage = PhpStorageFactory::get('service_container');
}
return $this->storage;
}
}
......@@ -25,8 +25,8 @@ interface DrupalKernelInterface extends KernelInterface {
*
* @param array $module_list
* The new list of modules.
* @param array $module_path
* @param array $module_paths
* List of module paths, keyed by module name.
*/