diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 95adc95..10b94e1 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 0b95dcd..12f93dc 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -5108,9 +5108,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/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php new file mode 100644 index 0000000..04ec59f --- /dev/null +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -0,0 +1,65 @@ +register('dispatcher', 'Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher') + ->addArgument(new Reference('service_container')); + $container->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver'); + $container->register('http_kernel', 'Symfony\Component\HttpKernel\HttpKernel') + ->addArgument(new Reference('dispatcher')) + ->addArgument(new Reference('resolver')); + $container->register('matcher', 'Drupal\Core\LegacyUrlMatcher'); + $container->register('router_listener', 'Drupal\Core\EventSubscriber\RouterListener') + ->addArgument(new Reference('matcher')) + ->addTag('kernel.event_subscriber'); + $container->register('content_negotiation', 'Drupal\Core\ContentNegotiation'); + $container->register('view_subscriber', 'Drupal\Core\EventSubscriber\ViewSubscriber') + ->addArgument(new Reference('content_negotiation')) + ->addTag('kernel.event_subscriber'); + $container->register('access_subscriber', 'Drupal\Core\EventSubscriber\AccessSubscriber') + ->addTag('kernel.event_subscriber'); + $container->register('maintenance_mode_subscriber', 'Drupal\Core\EventSubscriber\MaintenanceModeSubscriber') + ->addTag('kernel.event_subscriber'); + $container->register('path_subscriber', 'Drupal\Core\EventSubscriber\PathSubscriber') + ->addTag('kernel.event_subscriber'); + $container->register('legacy_request_subscriber', 'Drupal\Core\EventSubscriber\LegacyRequestSubscriber') + ->addTag('kernel.event_subscriber'); + $container->register('legacy_controller_subscriber', 'Drupal\Core\EventSubscriber\LegacyControllerSubscriber') + ->addTag('kernel.event_subscriber'); + $container->register('finish_response_subscriber', 'Drupal\Core\EventSubscriber\FinishResponseSubscriber') + ->addTag('kernel.event_subscriber'); + $container->register('request_close_subscriber', 'Drupal\Core\EventSubscriber\RequestCloseSubscriber') + ->addTag('kernel.event_subscriber'); + $container->register('database', 'Drupal\Core\Database\Connection') + ->setFactoryClass('Drupal\Core\Database\Database') + ->setFactoryMethod('getConnection') + ->addArgument('default'); + $container->register('database.slave', 'Drupal\Core\Database\Connection') + ->setFactoryClass('Drupal\Core\Database\Database') + ->setFactoryMethod('getConnection') + ->addArgument('slave'); + $container->register('exception_listener', 'Symfony\Component\HttpKernel\EventListener\ExceptionListener') + ->addTag('kernel.event_subscriber') + ->addArgument(new Reference('service_container')) + ->setFactoryClass('Drupal\Core\ExceptionController') + ->setFactoryMethod('getExceptionListener'); + + // Add a compiler pass for registering event subscribers. + $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING); + } +} 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..55343dc --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php @@ -0,0 +1,37 @@ +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..75a31bf 100644 --- a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php @@ -7,113 +7,33 @@ 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(); - - // 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 = new Compiler(); } - /** - * 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'))); + public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION) { + $this->compiler->addPass($pass, $type); + } - return $dispatcher; + public function compile() { + $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/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php new file mode 100644 index 0000000..7d372ad --- /dev/null +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -0,0 +1,82 @@ +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..2c53cda 100644 --- a/core/lib/Drupal/Core/ExceptionController.php +++ b/core/lib/Drupal/Core/ExceptionController.php @@ -7,12 +7,14 @@ namespace Drupal\Core; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\Exception\FlattenException; +use Symfony\Component\HttpKernel\EventListener\ExceptionListener; /** * This controller handles HTTP errors generated by the routing system. @@ -27,6 +29,22 @@ class ExceptionController extends ContainerAware { protected $negotiation; /** + * Factory method for getting an Exception Listener. Since this needs to be + * instanciated with a controller callable, i.e. an ExceptionConroller object + * and the name of the method to call, we can't just register it to the DIC + * the regular way. + * + * @todo: This probably doesn't belong here, but I'm not sure where would be a better + * place to put it... in a class of its own? + */ + public static function getExceptionListener(Container $container) { + $negotiation = $container->get('content_negotiation'); + $exceptionController = new self($negotiation); + $exceptionController->setContainer($container); + return new ExceptionListener(array($exceptionController, 'execute')); + } + + /** * Constructor. * * @param Drupal\Core\ContentNegotiation $negotiation @@ -107,7 +125,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 +190,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/core/modules/comment/comment.module b/core/modules/comment/comment.module index 9f47d37..f442a46 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -509,7 +509,7 @@ function comment_permalink($cid) { // @todo: Convert the pager to use the request object. $_GET['page'] = $page; request($subrequest); - return drupal_container()->get('httpkernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST); + return drupal_container()->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST); } throw new NotFoundHttpException(); } diff --git a/core/modules/system/tests/http.php b/core/modules/system/tests/http.php index 9364685..c91dd0c 100644 --- a/core/modules/system/tests/http.php +++ b/core/modules/system/tests/http.php @@ -5,6 +5,7 @@ * Fake an HTTP request, for use during testing. */ +use Drupal\Core\DrupalKernel; use Symfony\Component\HttpFoundation\Request; // Set a global variable to indicate a mock HTTP request. @@ -36,6 +37,7 @@ request($request); drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE); -$kernel = drupal_container()->get('httpkernel'); +$kernel = new DrupalKernel('prod', FALSE); +$kernel->boot(); $response = $kernel->handle($request)->prepare($request)->send(); $kernel->terminate($request, $response); diff --git a/core/modules/system/tests/https.php b/core/modules/system/tests/https.php index 11b7e13..5bf000b 100644 --- a/core/modules/system/tests/https.php +++ b/core/modules/system/tests/https.php @@ -5,6 +5,7 @@ * Fake an https request, for use during testing. */ +use Drupal\Core\DrupalKernel; use Symfony\Component\HttpFoundation\Request; // Set a global variable to indicate a mock HTTPS request. @@ -35,6 +36,7 @@ request($request); drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE); -$kernel = drupal_container()->get('httpkernel'); +$kernel = new DrupalKernel('prod', FALSE); +$kernel->boot(); $response = $kernel->handle($request)->prepare($request)->send(); $kernel->terminate($request, $response); diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc index b568f66..a57e7d9 100644 --- a/core/modules/user/user.pages.inc +++ b/core/modules/user/user.pages.inc @@ -500,7 +500,7 @@ function user_page() { // @todo: Cleaner sub request handling. $subrequest = Request::create('/user/' . $user->uid, 'GET', request()->query->all(), request()->cookies->all(), array(), request()->server->all()); request($subrequest); - return drupal_container()->get('httpkernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST); + return drupal_container()->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST); } else { return drupal_get_form('user_login'); diff --git a/index.php b/index.php index 7b99d10..200a2de 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; /** @@ -36,6 +37,9 @@ request($request); // @see Drupal\Core\EventSubscriber\LegacyRequestSubscriber; drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE); -$kernel = drupal_container()->get('httpkernel'); +$kernel = new DrupalKernel('prod', FALSE); +$kernel->boot(); + $response = $kernel->handle($request)->prepare($request)->send(); + $kernel->terminate($request, $response);