diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index fa1335b..da2dfd9 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -3,7 +3,8 @@ 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\HttpFoundation\Request; use Drupal\Core\Language\Language; @@ -2433,11 +2434,11 @@ function drupal_get_bootstrap_phase() { * @param $reset * A new container instance to reset the Drupal container to. * - * @return Drupal\Component\DependencyInjection\ContainerBuilder + * @return Symfony\Component\DependencyInjection\Container * 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. @@ -2446,7 +2447,15 @@ function drupal_container(ContainerBuilder $reset = NULL) { $container = $reset; } elseif (!isset($container)) { + // This will only ever happen if an error has been thrown. Just build a new + // ContainerBuilder with only the language_interface and language_content + // services. $container = new ContainerBuilder(); + // An interface language always needs to be available for t() and other + // functions. This default is overridden by drupal_language_initialize() + // during language negotiation. + $container->register(LANGUAGE_TYPE_INTERFACE, 'Drupal\\Core\\Language\\Language'); + $container->register(LANGUAGE_TYPE_CONTENT, 'Drupal\\Core\\Language\\Language'); } return $container; } @@ -2597,31 +2606,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/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterKernelListenersPass.php new file mode 100644 index 0000000..291e54b --- /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 aa73ace..680d10e 100644 --- a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php @@ -8,24 +8,90 @@ namespace Drupal\Core\DependencyInjection; use Symfony\Component\DependencyInjection\ContainerBuilder as BaseContainerBuilder; +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'); + $this->compiler->addPass($pass, $type); + $this->addObjectResource($pass); + } - // Register the default language content. - $this->register(LANGUAGE_TYPE_CONTENT, 'Drupal\\Core\\Language\\Language'); - } + + public function compile() + { + if (null === $this->compiler) { + $this->compiler = new Compiler(); + } + + foreach ($this->compiler->getPassConfig()->getPasses() as $pass) { + $this->addObjectResource($pass); + } + + $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()); + } + + + + /** + * Adds a resource for this configuration. + * + * This method is based on the addResource method in the parent class, but + * that method is reliant on the Config component which we do not use here. + * + * @param string $resource_filename A resource instance + * + * @return ContainerBuilder The current instance + * + * @api + */ + public function addDrupalFileResource($resource_filename) + { + $this->resources[] = $resource_filename; + return $this; + } + + /** + * Returns an array of resources loaded to build this configuration. + * + * @return string[] An array of resources + * + * @api + */ + public function getResources() + { + return array_unique($this->resources); + } + + /** + * Adds the object class hierarchy as resources. + * + * @param object $object An object instance + * + * @api + */ + public function addObjectResource($object) + { + $parent = new \ReflectionObject($object); + do { + $this->addDrupalFileResource($parent->getFileName()); + } while ($parent = $parent->getParentClass()); + } } diff --git a/core/lib/Drupal/Core/DrupalBundle.php b/core/lib/Drupal/Core/DrupalBundle.php new file mode 100644 index 0000000..1248c4b --- /dev/null +++ b/core/lib/Drupal/Core/DrupalBundle.php @@ -0,0 +1,167 @@ +getDefinitions(); + + foreach ($definitions as $id => $info) { + $info += array( + 'tags' => array(), + 'references' => array(), + 'methods' => array(), + ); + + $references = array(); + foreach ($info['references'] as $ref_id) { + $references[] = new Reference($ref_id); + } + + $definition = new Definition($info['class'], $references); + + foreach($info['tags'] as $tag) { + $definition->addTag($tag); + } + + foreach ($info['methods'] as $method => $args) { + $definition->addMethodCall($method, $args); + } + + $container->setDefinition($id, $definition); + } + + $this->registerLanguages($container); + + // 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', + ) + ), + 'cache_wamer' => array( + 'class' => 'Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate', + ), + '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'), + 'methods' => array('setContainer' => array('service_container')) + ), + 'exception_listener' => array( + 'class' => 'Symfony\Component\HttpKernel\EventListener\ExceptionListener', + 'references' => array('exception_controller'), + 'tags' => array('kernel.event_subscriber') + ), + ); + } + + /** + * Registers language-related services to the container. + */ + function registerLanguages($container) { + + $types = language_types_get_all(); + + // 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); + // 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)); + } + } + } +} \ No newline at end of file diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 7361a82..8489a44 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -7,59 +7,164 @@ namespace Drupal\Core; -use Symfony\Component\HttpKernel\HttpKernel; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; -use Symfony\Component\HttpKernel\EventListener\ExceptionListener; -use Drupal\Core\EventSubscriber\ViewSubscriber; -use Drupal\Core\EventSubscriber\AccessSubscriber; -use Drupal\Core\EventSubscriber\FinishResponseSubscriber; -use Drupal\Core\EventSubscriber\PathSubscriber; -use Drupal\Core\EventSubscriber\LegacyRequestSubscriber; -use Drupal\Core\EventSubscriber\LegacyControllerSubscriber; -use Drupal\Core\EventSubscriber\MaintenanceModeSubscriber; -use Drupal\Core\EventSubscriber\RequestCloseSubscriber; -use Drupal\Core\EventSubscriber\RouterListener; +use Drupal\Core\DrupalBundle; +use Symfony\Component\HttpKernel\Kernel; +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; /** * The DrupalKernel class is the core of Drupal itself. */ -class DrupalKernel extends HttpKernel { +class DrupalKernel extends Kernel { + + public function registerBundles() + { + $bundles = array( + new DrupalBundle(), + ); + $modules = array_keys(system_list('module_enabled')); + foreach ($modules as $module) { + $class = "\Drupal\{$module}\{$module}Bundle"; + if (class_exists($class)) { + $bundles[] = new $class(); + } + } + return $bundles; + } + + + /** + * Initializes the service container. + */ + protected function initializeContainer() + { + $class = $this->getContainerClass(); + + $cache_file = config_get_config_directory() . '/' . $class . '.php'; + $metadata = $cache_file . '.meta'; + + $fresh = TRUE; + + if (!is_file($cache_file) || !is_file($metadata)) { + $fresh = FALSE; + } + else { + $timestamp = filemtime($cache_file); + $meta = unserialize(file_get_contents($metadata)); + foreach ($meta as $resource) { + if (!file_exists($resource)) { + $fresh = FALSE; + break; + } + else { + if (filemtime($resource) >= $timestamp) { + $fresh = FALSE; + break; + } + } + } + } + + if (!$fresh) { + $container = $this->buildContainer(); + $this->dumpDrupalContainer($cache_file, $container, $class, $this->getContainerBaseClass()); + } + + require_once $cache_file; + + $this->container = new $class(); + $this->container->set('kernel', $this); + + if (!$fresh && $this->container->has('cache_warmer')) { + $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir')); + } + + drupal_container($this->container); + } /** - * Constructor. + * Builds the service container. * - * @param Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher - * An EventDispatcherInterface instance. - * @param Symfony\Component\HttpKernel\Controller\ControllerResolverInterface $resolver - * A ControllerResolverInterface instance. + * @return ContainerBuilder The compiled service container */ - public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver) { - parent::__construct($dispatcher, $resolver); - - $this->matcher = new LegacyUrlMatcher(); - $this->dispatcher->addSubscriber(new RouterListener($this->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. - $this->dispatcher->addSubscriber(new ViewSubscriber($negotiation)); - $this->dispatcher->addSubscriber(new AccessSubscriber()); - $this->dispatcher->addSubscriber(new MaintenanceModeSubscriber()); - $this->dispatcher->addSubscriber(new PathSubscriber()); - $this->dispatcher->addSubscriber(new LegacyRequestSubscriber()); - $this->dispatcher->addSubscriber(new LegacyControllerSubscriber()); - $this->dispatcher->addSubscriber(new FinishResponseSubscriber()); - $this->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. - $this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController($this, $negotiation), 'execute'))); + protected function buildContainer() + { + $container = $this->getContainerBuilder(); + foreach ($this->bundles as $bundle) { + $bundle->build($container); + } + $container->addObjectResource($this); + + $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())); + } + + /** + * Dumps the service container to PHP code in the config directory. + * + * This method is based on the dumpContinaer method in the parent class, but + * that method is reliant on the Config component which we do not use here. + * + * @param string $cache_file The full filename to write to. + * @param ContainerBuilder $container The service container + * @param string $class The name of the class to generate + * @param string $baseClass The name of the container's base class + */ + protected function dumpDrupalContainer($cache_file, ContainerBuilder $container, $class, $baseClass) + { + // cache the container + $dumper = new PhpDumper($container); + $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass)); + + if (!$this->debug) { + $content = self::stripComments($content); + } + $metadata = $container->getResources(); + + $dir = dirname($cache_file); + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true)) { + throw new \RuntimeException(sprintf('Unable to create the %s directory', $dir)); + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(sprintf('Unable to write in the %s directory', $dir)); + } + + $tmpFile = tempnam(dirname($cache_file), basename($cache_file)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $cache_file)) { + @chmod($cache_file, 0666 & ~umask()); + } else { + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $cache_file)); + } + + if (null !== $metadata && true === $this->debug) { + $file = $cache_file.'.meta'; + $tmpFile = tempnam(dirname($file), basename($file)); + if (false !== @file_put_contents($tmpFile, serialize($metadata)) && @rename($tmpFile, $file)) { + @chmod($file, 0666 & ~umask()); + } + } + } + + + 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 b163395..cb068b4 100644 --- a/core/lib/Drupal/Core/ExceptionController.php +++ b/core/lib/Drupal/Core/ExceptionController.php @@ -7,25 +7,17 @@ namespace Drupal\Core; +use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\Exception\FlattenException; /** * This controller handles HTTP errors generated by the routing system. */ -class ExceptionController { - - /** - * The kernel that spawned this controller. - * - * We will use this to fire subrequests as needed. - * - * @var Symfony\Component\HttpKernel\HttpKernelInterface - */ - protected $kernel; +class ExceptionController extends ContainerAware { /** * The content negotiation library. @@ -37,15 +29,11 @@ class ExceptionController { /** * Constructor. * - * @param Symfony\Component\HttpKernel\HttpKernelInterface $kernel - * The kernel that spawned this controller, so that it can be reused - * for subrequests. * @param Drupal\Core\ContentNegotiation $negotiation * The content negotiation library to use to determine the correct response * format. */ - public function __construct(HttpKernelInterface $kernel, ContentNegotiation $negotiation) { - $this->kernel = $kernel; + public function __construct(ContentNegotiation $negotiation) { $this->negotiation = $negotiation; } @@ -119,7 +107,7 @@ class ExceptionController { drupal_static_reset('menu_set_active_trail'); menu_reset_static_cache(); - $response = $this->kernel->handle($subrequest, DrupalKernel::SUB_REQUEST); + $response = $this->container->get('http_kernel')->handle($subrequest, HttpKernel::SUB_REQUEST); $response->setStatusCode(403, 'Access denied'); } else { @@ -184,7 +172,7 @@ class ExceptionController { drupal_static_reset('menu_set_active_trail'); menu_reset_static_cache(); - $response = $this->kernel->handle($subrequest, HttpKernelInterface::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 2f05bb3..10e38af 100644 --- a/index.php +++ b/index.php @@ -13,8 +13,6 @@ use Drupal\Core\DrupalKernel; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\EventDispatcher\EventDispatcher; -use Symfony\Component\HttpKernel\Controller\ControllerResolver; /** * Root directory of Drupal installation. @@ -23,6 +21,7 @@ define('DRUPAL_ROOT', getcwd()); // Bootstrap the lowest level of what we need. require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); +drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); // Create a request object from the HTTPFoundation. $request = Request::createFromGlobals(); @@ -32,6 +31,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. @@ -39,9 +41,6 @@ request($request); // @see Drupal\Core\EventSubscriber\LegacyRequestSubscriber; drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE); -$dispatcher = new EventDispatcher(); -$resolver = new ControllerResolver(); - -$kernel = new DrupalKernel($dispatcher, $resolver); $response = $kernel->handle($request)->prepare($request)->send(); + $kernel->terminate($request, $response);