Commit b1c684b8 authored by alexpott's avatar alexpott

Issue #1988508 by katbailey: Stop pretending we support Symfony bundles when...

Issue #1988508 by katbailey: Stop pretending we support Symfony bundles when we really just have service providers.
parent 77947830
......@@ -1889,8 +1889,7 @@ function drupal_handle_request($test_only = FALSE) {
exit;
}
// @todo Figure out how best to handle the Kernel constructor parameters.
$kernel = new DrupalKernel('prod', FALSE, drupal_classloader(), !$test_only);
$kernel = new DrupalKernel('prod', drupal_classloader(), !$test_only);
// @todo Remove this once everything in the bootstrap has been
// converted to services in the DIC.
......@@ -2026,7 +2025,7 @@ function _drupal_bootstrap_kernel() {
// Normally, index.php puts a container in drupal_container() by creating a
// kernel. If there is no container yet, create one.
if (!drupal_container()) {
$kernel = new DrupalKernel('prod', FALSE, drupal_classloader());
$kernel = new DrupalKernel('prod', drupal_classloader());
$kernel->boot();
}
}
......
......@@ -4,14 +4,14 @@
use Drupal\Core\Config\FileStorage;
use Drupal\Core\DrupalKernel;
use Drupal\Core\CoreBundle;
use Drupal\Core\CoreServiceProvider;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Install\TaskException;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\StringTranslation\Translator\FileTranslation;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
......@@ -354,7 +354,7 @@ function install_begin_request(&$install_state) {
// @see drupal_install_config_directories()
// @see install_settings_form_submit()
if ($install_state['settings_verified']) {
$kernel = new DrupalKernel('install', FALSE, drupal_classloader(), FALSE);
$kernel = new DrupalKernel('install', drupal_classloader(), FALSE);
$kernel->boot();
$container = $kernel->getContainer();
// Add the file translation service to the container.
......@@ -426,7 +426,7 @@ function install_begin_request(&$install_state) {
$conf['keyvalue_expirable_default'] = 'keyvalue.expirable.null';
// Register Twig template engine for use during install.
CoreBundle::registerTwig($container);
CoreServiceProvider::registerTwig($container);
$container->register('url_generator', 'Drupal\Core\Routing\NullGenerator');
......
......@@ -619,7 +619,7 @@ function drupal_install_system() {
if (!drupal_container()->has('kernel')) {
// Immediately boot a kernel to have real services ready.
$kernel = new DrupalKernel('install', FALSE, drupal_classloader(), FALSE);
$kernel = new DrupalKernel('install', drupal_classloader(), FALSE);
$kernel->boot();
}
......
......@@ -103,10 +103,9 @@ function update_prepare_d8_bootstrap() {
$settings['cache']['default'] = 'cache.backend.memory';
new Settings($settings);
// Enable UpdateBundle service overrides. While the container_bundles array
// does not need a key, let's use so it can be removed once the upgrade are
// finished. @see update_flush_all_caches()
$GLOBALS['conf']['container_bundles']['UpdateBundle'] = 'Drupal\Core\DependencyInjection\UpdateBundle';
// Enable UpdateServiceProvider service overrides.
// @see update_flush_all_caches()
$GLOBALS['conf']['container_service_providers']['UpdateServiceProvider'] = 'Drupal\Core\DependencyInjection\UpdateServiceProvider';
// Check whether settings.php needs to be rewritten.
$settings_exist = !empty($GLOBALS['config_directories']);
......@@ -117,7 +116,7 @@ function update_prepare_d8_bootstrap() {
// Bootstrap the kernel.
// Do not attempt to dump and write it.
$kernel = new DrupalKernel('update', FALSE, drupal_classloader(), FALSE);
$kernel = new DrupalKernel('update', drupal_classloader(), FALSE);
$kernel->boot();
// If any of the required settings needs to be written, then settings.php
......@@ -440,7 +439,7 @@ function update_prepare_d8_bootstrap() {
$settings = settings()->getAll();
unset($settings['cache']['default']);
new Settings($settings);
$kernel = new DrupalKernel('update', FALSE, drupal_classloader(), FALSE);
$kernel = new DrupalKernel('update', drupal_classloader(), FALSE);
$kernel->boot();
}
......
......@@ -19,8 +19,8 @@
*
* The container is built by the kernel and passed in to this class which stores
* it statically. The container always contains the services from
* \Drupal\Core\CoreBundle, the bundles of enabled modules and any other bundles
* defined in $GLOBALS['conf']['container_bundles'].
* \Drupal\Core\CoreServiceProvider, the service providers of enabled modules and any other
* service providers defined in $GLOBALS['conf']['container_service_providers'].
*
* This class exists only to support legacy code that cannot be dependency
* injected. If your code needs it, consider refactoring it to be object
......
......@@ -2,12 +2,15 @@
/**
* @file
* Definition of Drupal\Core\CoreBundle.
* Definition of Drupal\Core\CoreServiceProvider.
*/
namespace Drupal\Core;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass;
......@@ -18,16 +21,14 @@
use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Scope;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
/**
* Bundle class for mandatory core services.
* ServiceProvider class for mandatory core services.
*
* This is where Drupal core registers all of its compiler passes.
* The service definitions themselves are in core/core.services.yml with a
......@@ -36,12 +37,12 @@
* Modules wishing to register services to the container should use
* modulename.services.yml in their respective directories.
*/
class CoreBundle extends Bundle {
class CoreServiceProvider implements ServiceProviderInterface {
/**
* Implements \Symfony\Component\HttpKernel\Bundle\BundleInterface::build().
* {@inheritdoc}
*/
public function build(ContainerBuilder $container) {
public function register(ContainerBuilder $container) {
// 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).
......@@ -67,6 +68,9 @@ public function build(ContainerBuilder $container) {
// Add the compiler pass that will process the tagged breadcrumb builder
// services.
$container->addCompilerPass(new RegisterBreadcrumbBuilderPass());
// Add the compiler pass that lets service providers modify existing
// service definitions.
$container->addCompilerPass(new ModifyServiceDefinitionsPass());
}
/**
......
<?php
/**
* @file
* Contains Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Drupal\Core\DrupalKernelInterface;
use Drupal\Core\DependencyInjection\ServiceModifierInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Passes the container to the alter() method of all service providers.
*/
class ModifyServiceDefinitionsPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->has('kernel')) {
return;
}
$kernel = $container->get('kernel');
if (!($kernel instanceof DrupalKernelInterface)) {
return;
}
$providers = $kernel->getServiceProviders();
foreach ($providers as $provider) {
if ($provider instanceof ServiceModifierInterface) {
$provider->alter($container);
}
}
}
}
<?php
namespace Drupal\Core\DependencyInjection;
/**
* Interface that service providers can implement to modify services.
*/
interface ServiceModifierInterface {
/**
* Modifies existing service definitions.
*
* @param ContainerBuilder $container
* The ContainerBuilder whose service definitions can be altered.
*/
public function alter(ContainerBuilder $container);
}
<?php
namespace Drupal\Core\DependencyInjection;
/**
* Interface that all service providers must implement.
*/
interface ServiceProviderInterface {
/**
* Registers services to the container.
*
* @param ContainerBuilder $container
* The ContainerBuilder to register services to.
*/
public function register(ContainerBuilder $container);
}
......@@ -2,27 +2,27 @@
/**
* @file
* Contains \Drupal\Core\DependencyInjection\UpdateBundle.
* Contains \Drupal\Core\DependencyInjection\UpdateServiceProvider.
*/
namespace Drupal\Core\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder as SymfonyContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
/**
* Bundle class for update.php service overrides.
* ServiceProvider class for update.php service overrides.
*
* This bundle is manually added by update.php via $conf['container_bundles']
* This class is manually added by update.php via $conf['container_service_providers']
* and required to prevent various services from trying to retrieve data from
* storages that do not exist yet.
*/
class UpdateBundle extends Bundle {
class UpdateServiceProvider implements ServiceProviderInterface {
/**
* Implements \Symfony\Component\HttpKernel\Bundle\BundleInterface::build().
* {@inheritdoc}
*/
public function build(SymfonyContainerBuilder $container) {
public function register(ContainerBuilder $container) {
// Disable the Lock service.
$container
->register('lock', 'Drupal\Core\Lock\NullLockBackend');
......
......@@ -9,26 +9,55 @@
use Drupal\Component\PhpStorage\PhpStorageFactory;
use Drupal\Core\Config\BootstrapConfigStorageFactory;
use Drupal\Core\CoreBundle;
use Drupal\Core\CoreServiceProvider;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\YamlFileLoader;
use Symfony\Component\ClassLoader\ClassLoader;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
/**
* The DrupalKernel class is the core of Drupal itself.
*
* This class is responsible for building the Dependency Injection Container and
* also deals with the registration of bundles. It allows registered bundles to
* add their services to the container. Core provides the CoreBundle, which adds
* the services required for all core subsystems. Each module can then add its
* own bundle, i.e. a subclass of Symfony\Component\HttpKernel\Bundle, to
* register services to the container.
* also deals with the registration of service providers. It allows registered
* service providers to add their services to the container. Core provides the
* CoreServiceProvider, which, in addition to registering any core services that
* cannot be registered in the core.services.yaml file, adds any compiler passes
* needed by core, e.g. for processing tagged services. Each module can add its
* own service provider, i.e. a class implementing
* Drupal\Core\DependencyInjection\ServiceProvider, to register services to the
* container, or modify existing services.
*/
class DrupalKernel extends Kernel implements DrupalKernelInterface {
class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
const CONTAINER_BASE_CLASS = 'Container';
/**
* Holds the container instance.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* The environment, e.g. 'testing', 'install'.
*
* @var string
*/
protected $environment;
/**
* Whether the kernel has been booted.
*
* @var bool
*/
protected $booted;
/**
* Holds the list of enabled modules.
......@@ -80,11 +109,11 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface {
protected $configStorage;
/**
* The list of the classnames of the bundles in this kernel.
* The list of the classnames of the service providers in this kernel.
*
* @var array
*/
protected $bundleClasses;
protected $serviceProviderClasses;
/**
* Whether the container can be dumped.
......@@ -107,6 +136,13 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface {
*/
protected $serviceYamls;
/**
* The array of registered service providers.
*
* @var array
*/
protected $serviceProviders;
/**
* Constructs a DrupalKernel object.
*
......@@ -114,21 +150,18 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface {
* String indicating the environment, e.g. 'prod' or 'dev'. Used by
* Symfony\Component\HttpKernel\Kernel::__construct(). Drupal does not use
* this value currently. Pass 'prod'.
* @param bool $debug
* Boolean indicating whether we are in debug mode. Used by
* Symfony\Component\HttpKernel\Kernel::__construct(). Drupal does not use
* this value currently. Pass TRUE.
* @param \Symfony\Component\ClassLoader\ClassLoader $class_loader
* (optional) The classloader is only used if $storage is not given or
* 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.
* order to be able to find the module serviceProviders.
* @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, ClassLoader $class_loader, $allow_dumping = TRUE) {
parent::__construct($environment, $debug);
public function __construct($environment, ClassLoader $class_loader, $allow_dumping = TRUE) {
$this->environment = $environment;
$this->booted = false;
$this->classLoader = $class_loader;
$this->allowDumping = $allow_dumping;
}
......@@ -137,29 +170,19 @@ public function __construct($environment, $debug, ClassLoader $class_loader, $al
* {@inheritdoc}
*/
public function serialize() {
return serialize(array($this->environment, $this->debug, $this->classLoader, $this->allowDumping));
return serialize(array($this->environment, $this->classLoader, $this->allowDumping));
}
/**
* {@inheritdoc}
*/
public function unserialize($data) {
list($environment, $debug, $class_loader, $allow_dumping) = unserialize($data);
$this->__construct($environment, $debug, $class_loader, $allow_dumping);
}
/**
* Overrides Kernel::init().
*/
public function init() {
// Intentionally empty. The sole purpose is to not execute Kernel::init(),
// since that overrides/breaks Drupal's current error handling.
// @todo Investigate whether it is possible to migrate Drupal's error
// handling to the one of Kernel without losing functionality.
list($environment, $class_loader, $allow_dumping) = unserialize($data);
$this->__construct($environment, $class_loader, $allow_dumping);
}
/**
* Overrides Kernel::boot().
* {@inheritdoc}
*/
public function boot() {
if ($this->booted) {
......@@ -167,26 +190,41 @@ public function boot() {
}
$this->initializeContainer();
$this->booted = TRUE;
if ($this->containerNeedsDumping && !$this->dumpDrupalContainer($this->container, $this->getContainerBaseClass())) {
if ($this->containerNeedsDumping && !$this->dumpDrupalContainer($this->container, static::CONTAINER_BASE_CLASS)) {
watchdog('DrupalKernel', 'Container cannot be written to disk');
}
}
/**
* Returns an array of available bundles.
*
* @return array
* The available bundles.
* {@inheritdoc}
*/
public function registerBundles() {
public function shutdown() {
if (FALSE === $this->booted) {
return;
}
$this->booted = FALSE;
$this->container = null;
}
/**
* {@inheritdoc}
*/
public function getContainer() {
return $this->container;
}
/**
* {@inheritdoc}
*/
public function discoverServiceProviders() {
$this->configStorage = BootstrapConfigStorageFactory::get();
$bundles = array(
new CoreBundle(),
$serviceProviders = array(
'CoreServiceProvider' => new CoreServiceProvider(),
);
$this->serviceYamls = array(
'core/core.services.yml'
);
$this->bundleClasses = array('Drupal\Core\CoreBundle');
$this->serviceProviderClasses = array('Drupal\Core\CoreServiceProvider');
// Ensure we know what modules are enabled and that their namespaces are
// registered.
......@@ -197,13 +235,14 @@ public function registerBundles() {
$module_filenames = $this->getModuleFileNames();
$this->registerNamespaces($this->getModuleNamespaces($module_filenames));
// Load each module's bundle class.
// Load each module's serviceProvider class.
foreach ($this->moduleList as $module => $weight) {
$camelized = ContainerBuilder::camelize($module);
$class = "Drupal\\{$module}\\{$camelized}Bundle";
$name = "{$camelized}ServiceProvider";
$class = "Drupal\\{$module}\\{$name}";
if (class_exists($class)) {
$bundles[] = new $class();
$this->bundleClasses[] = $class;
$serviceProviders[$name] = new $class();
$this->serviceProviderClasses[] = $class;
}
$filename = dirname($module_filenames[$module]) . "/$module.services.yml";
if (file_exists($filename)) {
......@@ -211,18 +250,50 @@ public function registerBundles() {
}
}
// 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;
// Add site specific or test service providers.
if (!empty($GLOBALS['conf']['container_service_providers'])) {
foreach ($GLOBALS['conf']['container_service_providers'] as $name => $class) {
$serviceProviders[$name] = new $class();
$this->serviceProviderClasses[] = $class;
}
}
// Add site specific or test YAMLs.
if (!empty($GLOBALS['conf']['container_yamls'])) {
$this->serviceYamls = array_merge($this->serviceYamls, $GLOBALS['conf']['container_yamls']);
}
return $bundles;
return $serviceProviders;
}
/**
* {@inheritdoc}
*/
public function getServiceProviders() {
return $this->serviceProviders;
}
/**
* {@inheritdoc}
*/
public function terminate(Request $request, Response $response) {
if (FALSE === $this->booted) {
return;
}
if ($this->getHttpKernel() instanceof TerminableInterface) {
$this->getHttpKernel()->terminate($request, $response);
}
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) {
if (FALSE === $this->booted) {
$this->boot();
}
return $this->getHttpKernel()->handle($request, $type, $catch);
}
/**
......@@ -269,7 +340,7 @@ public function updateModules(array $module_list, array $module_filenames = arra
}
// If we haven't yet booted, we don't need to do anything: the new module
// list will take effect when boot() is called. If we have already booted,
// then reboot in order to refresh the bundle list and container.
// then reboot in order to refresh the serviceProvider list and container.
if ($this->booted) {
$this->booted = FALSE;
$this->boot();
......@@ -277,13 +348,13 @@ public function updateModules(array $module_list, array $module_filenames = arra
}
/**
* Returns the classname based on environment, debug and testing prefix.
* Returns the classname based on environment and testing prefix.
*
* @return string
* The class name.
*/
protected function getClassName() {
$parts = array('service_container', $this->environment, $this->debug);
$parts = array('service_container', $this->environment);
// Make sure to use a testing-specific container even in the parent site.
if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
$parts[] = $GLOBALS['drupal_test_info']['test_run_id'];
......@@ -294,6 +365,18 @@ protected function getClassName() {
return implode('_', $parts);
}
/**
* Returns the kernel parameters.
*
* @return array An array of kernel parameters
*/
protected function getKernelParameters() {
return array(
'kernel.environment' => $this->environment,
);
}
/**
* Initializes the service container.
*/
......@@ -371,6 +454,7 @@ protected function initializeContainer() {
}
$this->container->set('kernel', $this);
// Set the class loader which was registered as a synthetic service.
$this->container->set('class_loader', $this->classLoader);
// If we have a request set it back to the new container.
......@@ -416,9 +500,10 @@ protected function persistServices(array $persist) {
* @return ContainerBuilder The compiled service container
*/
protected function buildContainer() {
$this->initializeBundles();
$this->initializeServiceProviders();
$container = $this->getContainerBuilder();
$container->setParameter('container.bundles', $this->bundleClasses);
$container->set('kernel', $this);
$container->setParameter('container.service_providers', $this->serviceProviderClasses);
$container->setParameter('container.modules', $this->getModuleFileNames());
// Get a list of namespaces and put it onto the container.
......@@ -443,8 +528,8 @@ protected function buildContainer() {
foreach ($this->serviceYamls as $filename) {
$yaml_loader->load($filename);
}
foreach ($this->bundles as $bundle) {
$bundle->build($container);
foreach ($this->serviceProviders as $provider) {
$provider->register($container);
}
// Identify all services whose instances should be persisted when rebuilding
......@@ -463,6 +548,22 @@ protected function buildContainer() {
return $container;
}
/**
* Registers all service providers to the kernel.
*
* @throws \LogicException
*/
protected function initializeServiceProviders() {
$this->serviceProviders = array();
foreach ($this->discoverServiceProviders() as $name => $provider) {
if (isset($this->serviceProviders[$name])) {
throw new \LogicException(sprintf('Trying to register two service providers with the same name "%s"', $name));
}
$this->serviceProviders[$name] = $provider;
}
}
/**
* Gets a new ContainerBuilder instance used to build the service container.
*
......@@ -497,6 +598,16 @@ protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass)
return $this->storage()->save($class . '.php', $content);
}
/**
* Gets a http kernel from the container
*
* @return HttpKernel
*/
protected function getHttpKernel() {
return $this->container->get('http_kernel');
}
/**
* Overrides and eliminates this method from the parent class. Do not use.
*
......
......@@ -7,7 +7,7 @@
namespace Drupal\Core;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* The interface for DrupalKernel, the core of Drupal.
......@@ -15,7 +15,40 @@
* This interface extends Symfony's KernelInterface and adds methods for
* responding to modules being enabled or disabled during its lifetime.
*/
interface DrupalKernelInterface extends KernelInterface {
interface DrupalKernelInterface extends HttpKernelInterface, \Serializable {
/**
* Boots the current kernel.
*/
public function boot();
/**
* Shuts down the kernel.
*/
public function shutdown();
/**
* Discovers available serviceProviders.
*
* @return array
* The available serviceProviders.
*/
public function discoverServiceProviders();
/**
* Returns all registered service providers.
*
* @return array
* An associative array of ServiceProvider objects, keyed by name.
*/
public function getServiceProviders();
/**
* Gets the current container.
*
* @return ContainerInterface A ContainerInterface instance
*/
public function getContainer();
/**
* Updates the kernel's list of modules to the new list.
......
......@@ -81,7 +81,7 @@ class ModuleHandler implements ModuleHandlerInterface {