diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 0551e56..45bd869 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -3,7 +3,10 @@ use Drupal\Core\Database\Database; use Symfony\Component\ClassLoader\UniversalClassLoader; use Symfony\Component\ClassLoader\ApcUniversalClassLoader; -use Drupal\Core\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpFoundation\Request; use Drupal\Core\Language\Language; @@ -2460,7 +2463,7 @@ function drupal_get_bootstrap_phase() { * The instance of the Drupal Container used to set up and maintain object * instances. */ -function drupal_container(ContainerBuilder $reset = NULL) { +function drupal_container(Container $reset = NULL) { // We do not use drupal_static() here because we do not have a mechanism by // which to reinitialize the stored objects, so a drupal_static_reset() call // would leave Drupal in a nonfunctional state. @@ -2469,7 +2472,64 @@ function drupal_container(ContainerBuilder $reset = NULL) { $container = $reset; } elseif (!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. $container = new ContainerBuilder(); + // An interface language always needs to be available for t() and other + // functions. + $container->register(LANGUAGE_TYPE_INTERFACE, 'Drupal\\Core\\Language\\Language'); + // Register configuration storage dispatcher. + $container->setParameter('config.storage.info', array( + 'Drupal\Core\Config\DatabaseStorage' => array( + 'connection' => 'default', + 'target' => 'default', + 'read' => TRUE, + 'write' => TRUE, + ), + 'Drupal\Core\Config\FileStorage' => array( + 'directory' => config_get_config_directory(), + 'read' => TRUE, + 'write' => FALSE, + ), + )); + $container->register('config.storage.dispatcher', 'Drupal\Core\Config\StorageDispatcher') + ->addArgument('%config.storage.info%'); + + // Register configuration object factory. + $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') + ->addArgument(new Reference('config.storage.dispatcher')); + + // Ensure a language object is registered for each language type, whether the + // site is multilingual or not. + $types = language_types_get_all(); + if (language_multilingual()) { + include_once DRUPAL_ROOT . '/core/includes/language.inc'; + foreach ($types as $type) { + $language = language_types_initialize($type); + // We cannot pass an object as a parameter to a method on a service. + $info = get_object_vars($language); + $container->set($type, NULL); + $container->register($type, 'Drupal\\Core\\Language\\Language') + ->addMethodCall('extend', array($info)); + } + } + else { + $info = variable_get('language_default', array( + 'langcode' => 'en', + 'name' => 'English', + 'direction' => 0, + 'weight' => 0, + 'locked' => 0, + )); + $info['default'] = TRUE; + foreach ($types as $type) { + $container->set($type, NULL); + $container->register($type, 'Drupal\\Core\\Language\\Language') + ->addMethodCall('extend', array($info)); + } + } } return $container; } @@ -2620,31 +2680,11 @@ function get_t() { * @see Drupal\Core\Language\Language */ function drupal_language_initialize() { - $types = language_types_get_all(); - $container = drupal_container(); - - // Ensure a language object is registered for each language type, whether the - // site is multilingual or not. if (language_multilingual()) { - include_once DRUPAL_ROOT . '/core/includes/language.inc'; - foreach ($types as $type) { - $language = language_types_initialize($type); - $container->set($type, NULL); - $container->register($type, 'Drupal\\Core\\Language\\Language') - ->addMethodCall('extend', array($language)); - } // Allow modules to react on language system initialization in multilingual // environments. bootstrap_invoke_all('language_init'); } - else { - $default = language_default(); - foreach ($types as $type) { - $container->set($type, NULL); - $container->register($type, 'Drupal\\Core\\Language\\Language') - ->addMethodCall('extend', array($default)); - } - } } /** diff --git a/core/includes/common.inc b/core/includes/common.inc index 8b53cc6..689d136 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -5104,9 +5104,9 @@ function _drupal_bootstrap_code() { } /** - * Temporary BC function for scripts not using HttpKernel. + * Temporary BC function for scripts not using DrupalKernel. * - * HttpKernel skips this and replicates it via event listeners. + * DrupalKernel skips this and replicates it via event listeners. * * @see Drupal\Core\EventSubscriber\PathSubscriber; * @see Drupal\Core\EventSubscriber\LegacyRequestSubscriber; diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 32ce838..a0097c7 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1,5 +1,6 @@ boot(); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); } diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php new file mode 100644 index 0000000..79fbe7c --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php @@ -0,0 +1,36 @@ +hasDefinition('dispatcher')) { + return; + } + + $definition = $container->getDefinition('dispatcher'); + + foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $id => $attributes) { + + // We must assume that the class value has been correcly filled, even if the service is created by a factory + $class = $container->getDefinition($id)->getClass(); + + $refClass = new \ReflectionClass($class); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + if (!$refClass->implementsInterface($interface)) { + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} diff --git a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php index a82f5b6..fe4541f 100644 --- a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php @@ -7,113 +7,38 @@ namespace Drupal\Core\DependencyInjection; -use Drupal\Core\ContentNegotiation; -use Drupal\Core\EventSubscriber\AccessSubscriber; -use Drupal\Core\EventSubscriber\FinishResponseSubscriber; -use Drupal\Core\EventSubscriber\LegacyControllerSubscriber; -use Drupal\Core\EventSubscriber\LegacyRequestSubscriber; -use Drupal\Core\EventSubscriber\MaintenanceModeSubscriber; -use Drupal\Core\EventSubscriber\PathSubscriber; -use Drupal\Core\EventSubscriber\RequestCloseSubscriber; -use Drupal\Core\EventSubscriber\RouterListener; -use Drupal\Core\EventSubscriber\ViewSubscriber; -use Drupal\Core\ExceptionController; -use Drupal\Core\LegacyUrlMatcher; use Symfony\Component\DependencyInjection\ContainerBuilder as BaseContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\EventDispatcher\EventDispatcher; -use Symfony\Component\HttpKernel\EventListener\ExceptionListener; +use Symfony\Component\DependencyInjection\Compiler\Compiler; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; /** * Drupal's dependency injection container. */ class ContainerBuilder extends BaseContainerBuilder { - /** - * Registers the base Drupal services for the dependency injection container. - */ - public function __construct() { - parent::__construct(); + public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION) + { + if (!isset($this->compiler) || null === $this->compiler) { + $this->compiler = new Compiler(); + } - // An interface language always needs to be available for t() and other - // functions. This default is overridden by drupal_language_initialize() - // during language negotiation. - $this->register(LANGUAGE_TYPE_INTERFACE, 'Drupal\\Core\\Language\\Language'); - - // Register the default language content. - $this->register(LANGUAGE_TYPE_CONTENT, 'Drupal\\Core\\Language\\Language'); - - // Register configuration storage dispatcher. - $this->setParameter('config.storage.info', array( - 'Drupal\Core\Config\DatabaseStorage' => array( - 'connection' => 'default', - 'target' => 'default', - 'read' => TRUE, - 'write' => TRUE, - ), - 'Drupal\Core\Config\FileStorage' => array( - 'directory' => config_get_config_directory(), - 'read' => TRUE, - 'write' => FALSE, - ), - )); - $this->register('config.storage.dispatcher', 'Drupal\Core\Config\StorageDispatcher') - ->addArgument('%config.storage.info%'); - - // Register configuration object factory. - $this->register('config.factory', 'Drupal\Core\Config\ConfigFactory') - ->addArgument(new Reference('config.storage.dispatcher')); - - // Register the HTTP kernel services. - $this->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher') - ->addArgument(new Reference('service_container')) - ->setFactoryClass('Drupal\Core\DependencyInjection\ContainerBuilder') - ->setFactoryMethod('getKernelEventDispatcher'); - $this->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver'); - $this->register('httpkernel', 'Symfony\Component\HttpKernel\HttpKernel') - ->addArgument(new Reference('dispatcher')) - ->addArgument(new Reference('resolver')); + $this->compiler->addPass($pass, $type); } - /** - * Creates an EventDispatcher for the HttpKernel. Factory method. - * - * @param Drupal\Core\DependencyInjection\ContainerBuilder $container - * The dependency injection container that contains the HTTP kernel. - * - * @return Symfony\Component\EventDispatcher\EventDispatcher - * An EventDispatcher with the default listeners attached to it. - */ - public static function getKernelEventDispatcher($container) { - $dispatcher = new EventDispatcher(); - - $matcher = new LegacyUrlMatcher(); - $dispatcher->addSubscriber(new RouterListener($matcher)); - - $negotiation = new ContentNegotiation(); - - // @todo Make this extensible rather than just hard coding some. - // @todo Add a subscriber to handle other things, too, like our Ajax - // replacement system. - $dispatcher->addSubscriber(new ViewSubscriber($negotiation)); - $dispatcher->addSubscriber(new AccessSubscriber()); - $dispatcher->addSubscriber(new MaintenanceModeSubscriber()); - $dispatcher->addSubscriber(new PathSubscriber()); - $dispatcher->addSubscriber(new LegacyRequestSubscriber()); - $dispatcher->addSubscriber(new LegacyControllerSubscriber()); - $dispatcher->addSubscriber(new FinishResponseSubscriber()); - $dispatcher->addSubscriber(new RequestCloseSubscriber()); - - // Some other form of error occured that wasn't handled by another kernel - // listener. That could mean that it's a method/mime-type/error combination - // that is not accounted for, or some other type of error. Either way, treat - // it as a server-level error and return an HTTP 500. By default, this will - // be an HTML-type response because that's a decent best guess if we don't - // know otherwise. - $exceptionController = new ExceptionController($negotiation); - $exceptionController->setContainer($container); - $dispatcher->addSubscriber(new ExceptionListener(array($exceptionController, 'execute'))); - - return $dispatcher; + public function compile() + { + if (null === $this->compiler) { + $this->compiler = new Compiler(); + } + + $this->compiler->compile($this); + $this->parameterBag->resolve(); + // TODO: The line below is commented out because there is code that calls + // the set() method on the container after it has been built - that method + // throws an exception if the container's parameters have been frozen. + //$this->parameterBag = new FrozenParameterBag($this->parameterBag->all()); } + } diff --git a/core/lib/Drupal/Core/DrupalBundle.php b/core/lib/Drupal/Core/DrupalBundle.php new file mode 100644 index 0000000..bc822e3 --- /dev/null +++ b/core/lib/Drupal/Core/DrupalBundle.php @@ -0,0 +1,154 @@ +getDefinitions(); + + foreach ($definitions as $id => $info) { + $info += array( + 'tags' => array(), + 'references' => array(), + 'parameters' => array(), + 'methods' => array(), + 'arguments' => array(), + ); + + $references = array(); + foreach ($info['references'] as $ref_id) { + $references[] = new Reference($ref_id); + } + + $definition = new Definition($info['class'], $references); + + foreach ($info['parameters'] as $key => $param) { + $container->setParameter($key, $param); + $definition->addArgument("%{$key}%"); + } + + if (isset($info['factory_class']) && isset($info['factory_method'])) { + $definition->setFactoryClass($info['factory_class']); + $definition->setFactoryMethod($info['factory_method']); + } + + foreach ($info['arguments'] as $argument) { + $definition->addArgument($argument); + } + + foreach($info['tags'] as $tag) { + $definition->addTag($tag); + } + + foreach ($info['methods'] as $method => $args) { + $definition->addMethodCall($method, $args); + } + + if (isset($info['container_aware'])) { + $definition->addMethodCall('setContainer', array(new Reference('service_container'))); + } + $container->setDefinition($id, $definition); + } + + // Add a compiler pass for registering event subscribers. + $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING); + } + + /** + * Returns an array of definitions for the services we want to register. + */ + function getDefinitions() { + return array( + 'dispatcher' => array( + 'class' => 'Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher', + 'references' => array( + 'service_container', + ), + ), + 'resolver' => array( + 'class' => 'Symfony\Component\HttpKernel\Controller\ControllerResolver', + ), + 'http_kernel' => array( + 'class' => 'Symfony\Component\HttpKernel\HttpKernel', + 'references' => array( + 'dispatcher', 'resolver', + ) + ), + 'matcher' => array( + 'class' => 'Drupal\Core\LegacyUrlMatcher', + ), + 'router_listener' => array( + 'class' => 'Drupal\Core\EventSubscriber\RouterListener', + 'references' => array('matcher'), + 'tags' => array('kernel.event_subscriber') + ), + 'content_negotiation' => array( + 'class' => 'Drupal\Core\ContentNegotiation', + ), + 'view_subscriber' => array( + 'class' => 'Drupal\Core\EventSubscriber\ViewSubscriber', + 'references' => array('content_negotiation'), + 'tags' => array('kernel.event_subscriber') + ), + 'access_subscriber' => array( + 'class' => 'Drupal\Core\EventSubscriber\AccessSubscriber', + 'tags' => array('kernel.event_subscriber') + ), + 'maintenance_mode_subscriber' => array( + 'class' => 'Drupal\Core\EventSubscriber\MaintenanceModeSubscriber', + 'tags' => array('kernel.event_subscriber') + ), + 'path_subscriber' => array( + 'class' => 'Drupal\Core\EventSubscriber\PathSubscriber', + 'tags' => array('kernel.event_subscriber') + ), + 'legacy_request_subscriber' => array( + 'class' => 'Drupal\Core\EventSubscriber\LegacyRequestSubscriber', + 'tags' => array('kernel.event_subscriber') + ), + 'legacy_controller_subscriber' => array( + 'class' => 'Drupal\Core\EventSubscriber\LegacyControllerSubscriber', + 'tags' => array('kernel.event_subscriber') + ), + 'finish_response_subscriber' => array( + 'class' => 'Drupal\Core\EventSubscriber\FinishResponseSubscriber', + 'tags' => array('kernel.event_subscriber') + ), + 'request_close_subscriber' => array( + 'class' => 'Drupal\Core\EventSubscriber\RequestCloseSubscriber', + 'tags' => array('kernel.event_subscriber') + ), + 'exception_controller' => array( + 'class' => 'Drupal\Core\ExceptionController', + 'references' => array('content_negotiation'), + 'container_aware' => TRUE, + ), + 'exception_listener' => array( + 'class' => 'Symfony\Component\HttpKernel\EventListener\ExceptionListener', + 'references' => array('exception_controller'), + 'tags' => array('kernel.event_subscriber') + ), + 'database' => array( + 'class' => 'Drupal\Core\Database\Connection', + 'factory_class' => 'Drupal\Core\Database\Database', + 'factory_method' => 'getConnection', + 'arguments' => array('default'), + ), + 'database.slave' => array( + 'class' => 'Drupal\Core\Database\Connection', + 'factory_class' => 'Drupal\Core\Database\Database', + 'factory_method' => 'getConnection', + 'arguments' => array('slave'), + ), + ); + } +} diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php new file mode 100644 index 0000000..283e59c --- /dev/null +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -0,0 +1,94 @@ +container = $this->buildContainer(); + $this->container->set('kernel', $this); + drupal_container($this->container); + } + + /** + * Builds the service container. + * + * @return ContainerBuilder The compiled service container + */ + protected function buildContainer() + { + $container = $this->getContainerBuilder(); + + if ($bootstrap_container = drupal_container()) { + $container->merge($bootstrap_container); + } + foreach ($this->bundles as $bundle) { + $bundle->build($container); + } + $container->compile(); + return $container; + } + + + /** + * Gets a new ContainerBuilder instance used to build the service container. + * + * @return ContainerBuilder + */ + protected function getContainerBuilder() + { + return new ContainerBuilder(new ParameterBag($this->getKernelParameters())); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + // We have to define this method because it's not defined in the base class + // but is part of the KernelInterface interface. However, the LoaderInterface + // class is part of the config component, which we are not using, so this + // is badness :-/ + } +} diff --git a/core/lib/Drupal/Core/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php index 7f71e28..836ab56 100644 --- a/core/lib/Drupal/Core/ExceptionController.php +++ b/core/lib/Drupal/Core/ExceptionController.php @@ -107,7 +107,7 @@ class ExceptionController extends ContainerAware { drupal_static_reset('menu_set_active_trail'); menu_reset_static_cache(); - $response = $this->container->get('httpkernel')->handle($subrequest, HttpKernel::SUB_REQUEST); + $response = $this->container->get('http_kernel')->handle($subrequest, HttpKernel::SUB_REQUEST); $response->setStatusCode(403, 'Access denied'); } else { @@ -172,7 +172,7 @@ class ExceptionController extends ContainerAware { drupal_static_reset('menu_set_active_trail'); menu_reset_static_cache(); - $response = $this->container->get('httpkernel')->handle($subrequest, HttpKernel::SUB_REQUEST); + $response = $this->container->get('http_kernel')->handle($subrequest, HttpKernel::SUB_REQUEST); $response->setStatusCode(404, 'Not Found'); } else { diff --git a/core/lib/Drupal/Core/Language/Language.php b/core/lib/Drupal/Core/Language/Language.php index e774fa9..9ace41f 100644 --- a/core/lib/Drupal/Core/Language/Language.php +++ b/core/lib/Drupal/Core/Language/Language.php @@ -44,8 +44,8 @@ class Language { * * @todo Remove this function once $GLOBALS['language'] is gone. */ - public function extend($obj) { - $vars = get_object_vars($obj); + public function extend($info) { + $vars = is_array($info) ? $info : get_object_vars($info); foreach ($vars as $var => $value) { $this->$var = $value; } diff --git a/index.php b/index.php index 7b99d10..df4b2b6 100644 --- a/index.php +++ b/index.php @@ -11,6 +11,7 @@ * See COPYRIGHT.txt and LICENSE.txt files in the "core" directory. */ +use Drupal\Core\DrupalKernel; use Symfony\Component\HttpFoundation\Request; /** @@ -29,6 +30,9 @@ $request = Request::createFromGlobals(); // container at some point. request($request); +$kernel = new DrupalKernel('prod', FALSE); +$kernel->boot(); + // Bootstrap all of Drupal's subsystems, but do not initialize anything that // depends on the fully resolved Drupal path, because path resolution happens // during the REQUEST event of the kernel. @@ -36,6 +40,6 @@ request($request); // @see Drupal\Core\EventSubscriber\LegacyRequestSubscriber; drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE); -$kernel = drupal_container()->get('httpkernel'); $response = $kernel->handle($request)->prepare($request)->send(); + $kernel->terminate($request, $response);