Commit afc4b899 authored by catch's avatar catch

Issue #1706064 by chx, katbailey, effulgentsia, podarok et al: Compile the...

Issue #1706064 by chx, katbailey, effulgentsia, podarok et al: Compile the Injection Container to disk.
parent 408b1cf0
......@@ -894,7 +894,7 @@ function drupal_get_filename($type, $name, $filename = NULL) {
// Verify that we have an keyvalue service before using it. This is required
// because this function is called during installation.
// @todo Inject database connection into KeyValueStore\DatabaseStorage.
if (drupal_container()->hasDefinition('keyvalue') && function_exists('db_query')) {
if (drupal_container()->has('keyvalue') && function_exists('db_query')) {
try {
$file_list = state()->get('system.' . $type . '.files');
if ($file_list && isset($file_list[$name]) && file_exists(DRUPAL_ROOT . '/' . $file_list[$name])) {
......@@ -2442,7 +2442,7 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
// @todo Replace this with a cache.factory service plus 'config' argument.
$container
->register('cache.config')
->register('cache.config', 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryClass('Drupal\Core\Cache\CacheFactory')
->setFactoryMethod('get')
->addArgument('config');
......
......@@ -3901,7 +3901,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
// this is an AJAX request.
// @todo Clean up container call.
$container = drupal_container();
if ($container->has('request') && $container->has('content_negotiation')) {
if ($container->has('content_negotiation') && $container->isScopeActive('request')) {
$type = $container->get('content_negotiation')->getContentType($container->get('request'));
}
if (!empty($items['settings']) || (!empty($type) && $type == 'ajax')) {
......@@ -6853,6 +6853,9 @@ function drupal_flush_all_caches() {
drupal_container()->get('router.builder')->rebuild();
menu_router_rebuild();
// Wipe the DIC cache.
drupal_php_storage('service_container')->deleteAll();
// Re-initialize the maintenance theme, if the current request attempted to
// use it. Unlike regular usages of this function, the installer and update
// scripts need to flush all caches during GET requests/page building.
......
......@@ -1441,32 +1441,40 @@ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
$options['key'] = in_array($options['key'], array('uri', 'filename', 'name')) ? $options['key'] : 'uri';
$files = array();
if (is_dir($dir) && $handle = opendir($dir)) {
while (FALSE !== ($filename = readdir($handle))) {
if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') {
$uri = "$dir/$filename";
$uri = file_stream_wrapper_uri_normalize($uri);
if (is_dir($uri) && $options['recurse']) {
// Give priority to files in this folder by merging them in after any subdirectory files.
$files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
}
elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
// Always use this match over anything already set in $files with the
// same $$options['key'].
$file = new stdClass();
$file->uri = $uri;
$file->filename = $filename;
$file->name = pathinfo($filename, PATHINFO_FILENAME);
$key = $options['key'];
$files[$file->$key] = $file;
if ($options['callback']) {
$options['callback']($uri);
// Avoid warnings when opendir does not have the permissions to open a
// directory.
if (is_dir($dir)) {
if($handle = @opendir($dir)) {
while (FALSE !== ($filename = readdir($handle))) {
if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') {
$uri = "$dir/$filename";
$uri = file_stream_wrapper_uri_normalize($uri);
if (is_dir($uri) && $options['recurse']) {
// Give priority to files in this folder by merging them in after
// any subdirectory files.
$files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
}
elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
// Always use this match over anything already set in $files with
// the same $options['key'].
$file = new stdClass();
$file->uri = $uri;
$file->filename = $filename;
$file->name = pathinfo($filename, PATHINFO_FILENAME);
$key = $options['key'];
$files[$file->$key] = $file;
if ($options['callback']) {
$options['callback']($uri);
}
}
}
}
}
closedir($handle);
closedir($handle);
}
else {
watchdog('file', '@dir can not be opened', array('@dir' => $dir), WATCHDOG_ERROR);
}
}
return $files;
......
......@@ -1495,11 +1495,9 @@ function install_bootstrap_full(&$install_state) {
// Clear the module list that was overriden earlier in the process.
// This will allow all freshly installed modules to be loaded.
module_list_reset();
// @todo The constructor parameters for the Kernel class are for environment,
// e.g. 'prod', 'dev', and a boolean indicating whether it is in debug mode.
// Drupal does not currently make use of either of these, though that may
// change with http://drupal.org/node/1537198.
$kernel = new DrupalKernel('prod', FALSE);
// Instantiate the kernel.
$kernel = new DrupalKernel('prod', FALSE, NULL);
$kernel->boot();
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
}
......
......@@ -69,4 +69,18 @@ public function delete($name) {
protected function getFullPath($name) {
return $this->directory . '/' . $name;
}
/**
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::writeable().
*/
function writeable() {
return FALSE;
}
/**
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::deleteAll().
*/
public function deleteAll() {
return FALSE;
}
}
......@@ -71,4 +71,27 @@ public function delete($name) {
protected function getFullPath($name) {
return $this->directory . '/' . $name;
}
/**
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::writeable().
*/
function writeable() {
return TRUE;
}
/**
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::deleteAll().
*/
function deleteAll() {
return file_unmanaged_delete_recursive($this->directory, array(__CLASS__, 'filePreDeleteCallback'));
}
/**
* Ensures files and directories are deletable.
*/
public static function filePreDeleteCallback($path) {
if (file_exists($path)) {
chmod($path, 0700);
}
}
}
......@@ -56,6 +56,13 @@ public function load($name);
*/
public function save($name, $code);
/**
* Whether this is a writeable storage.
*
* @return bool
*/
public function writeable();
/**
* Deletes PHP code from storage.
*
......@@ -66,4 +73,9 @@ public function save($name, $code);
* TRUE if the delete succeeded, FALSE if it failed.
*/
public function delete($name);
/**
* Removes all files in this bin.
*/
public function deleteAll();
}
......@@ -8,6 +8,8 @@
namespace Drupal\Core;
use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
......@@ -26,6 +28,7 @@
*/
class CoreBundle extends Bundle
{
public function build(ContainerBuilder $container) {
// The 'request' scope and service enable services to depend on the Request
......@@ -61,41 +64,34 @@ public function build(ContainerBuilder $container) {
->addArgument(new Reference('database'))
->addArgument(new Reference('lock'));
$container->register('router.dumper', '\Drupal\Core\Routing\MatcherDumper')
$container->register('router.dumper', 'Drupal\Core\Routing\MatcherDumper')
->addArgument(new Reference('database'));
$container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
->addArgument(new Reference('router.dumper'));
// @todo Replace below lines with the commented out block below it when it's
// performant to do so: http://drupal.org/node/1706064.
$dispatcher = $container->get('dispatcher');
$matcher = new \Drupal\Core\Routing\ChainMatcher();
$matcher->add(new \Drupal\Core\LegacyUrlMatcher());
$nested = new \Drupal\Core\Routing\NestedMatcher();
$nested->setInitialMatcher(new \Drupal\Core\Routing\PathMatcher(Database::getConnection()));
$nested->addPartialMatcher(new \Drupal\Core\Routing\HttpMethodMatcher());
$nested->setFinalMatcher(new \Drupal\Core\Routing\FirstEntryFinalMatcher());
$matcher->add($nested, 5);
$container->register('matcher', 'Drupal\Core\Routing\ChainMatcher');
$container->register('legacy_url_matcher', 'Drupal\Core\LegacyUrlMatcher')
->addTag('chained_matcher');
$container->register('nested_matcher', 'Drupal\Core\Routing\NestedMatcher')
->addTag('chained_matcher', array('priority' => 5));
$content_negotation = new \Drupal\Core\ContentNegotiation();
$dispatcher->addSubscriber(new \Symfony\Component\HttpKernel\EventListener\RouterListener($matcher));
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\ViewSubscriber($content_negotation));
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\AccessSubscriber());
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\MaintenanceModeSubscriber());
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\PathSubscriber());
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\LegacyRequestSubscriber());
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\LegacyControllerSubscriber());
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\FinishResponseSubscriber());
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\RequestCloseSubscriber());
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber());
$dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\RouteProcessorSubscriber());
$container->set('content_negotiation', $content_negotation);
$dispatcher->addSubscriber(\Drupal\Core\ExceptionController::getExceptionListener($container));
// The following services are tagged as 'nested_matcher' services and are
// processed in the RegisterNestedMatchersPass compiler pass. Each one
// needs to be set on the matcher using a different method, so we use a
// tag attribute, 'method', which can be retrieved and passed to the
// addMethodCall() method that gets called on the matcher service in the
// compiler pass.
$container->register('path_matcher', 'Drupal\Core\Routing\PathMatcher')
->addArgument(new Reference('database'))
->addTag('nested_matcher', array('method' => 'setInitialMatcher'));
$container->register('http_method_matcher', 'Drupal\Core\Routing\HttpMethodMatcher')
->addTag('nested_matcher', array('method' => 'addPartialMatcher'));
$container->register('first_entry_final_matcher', 'Drupal\Core\Routing\FirstEntryFinalMatcher')
->addTag('nested_matcher', array('method' => 'setFinalMatcher'));
/*
$container->register('matcher', 'Drupal\Core\LegacyUrlMatcher');
$container->register('router_listener', 'Drupal\Core\EventSubscriber\RouterListener')
$container->register('router_processor_subscriber', 'Drupal\Core\EventSubscriber\RouteProcessorSubscriber')
->addTag('kernel.event_subscriber');
$container->register('router_listener', 'Symfony\Component\HttpKernel\EventListener\RouterListener')
->addArgument(new Reference('matcher'))
->addTag('kernel.event_subscriber');
$container->register('content_negotiation', 'Drupal\Core\ContentNegotiation');
......@@ -118,15 +114,18 @@ public function build(ContainerBuilder $container) {
->addTag('kernel.event_subscriber');
$container->register('request_close_subscriber', 'Drupal\Core\EventSubscriber\RequestCloseSubscriber')
->addTag('kernel.event_subscriber');
$container->register('config_global_override_subscriber', '\Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber');
$container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber')
->addTag('kernel.event_subscriber');
$container->register('exception_listener', 'Drupal\Core\EventSubscriber\ExceptionListener')
->addTag('kernel.event_subscriber')
->addArgument(new Reference('service_container'))
->setFactoryClass('Drupal\Core\ExceptionController')
->setFactoryMethod('getExceptionListener');
$container->addCompilerPass(new RegisterMatchersPass());
$container->addCompilerPass(new RegisterNestedMatchersPass());
// Add a compiler pass for registering event subscribers.
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
*/
}
}
<?php
/**
* @file
* Contains Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services tagged 'chained_matcher' to the 'matcher' service.
*/
class RegisterMatchersPass implements CompilerPassInterface {
/**
* Adds services tagged 'chained_matcher' to the 'matcher' service.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to process.
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('matcher')) {
return;
}
$matcher = $container->getDefinition('matcher');
foreach ($container->findTaggedServiceIds('chained_matcher') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$matcher->addMethodCall('add', array(new Reference($id), $priority));
}
}
}
<?php
/**
* @file
* Contains Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services tagged 'nested_matcher' to the tagged_matcher service.
*/
class RegisterNestedMatchersPass implements CompilerPassInterface {
/**
* Adds services tagged 'nested_matcher' to the tagged_matcher service.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to process.
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('nested_matcher')) {
return;
}
$nested = $container->getDefinition('nested_matcher');
foreach ($container->findTaggedServiceIds('nested_matcher') as $id => $attributes) {
$method = $attributes[0]['method'];
$nested->addMethodCall($method, array(new Reference($id)));
}
}
}
......@@ -7,11 +7,14 @@
namespace Drupal\Core;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\CoreBundle;
use Drupal\Component\PhpStorage\PhpStorageInterface;
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.
......@@ -25,6 +28,60 @@
*/
class DrupalKernel extends Kernel {
/**
* Holds the list of enabled modules.
*
* @var array
*/
protected $moduleList;
/**
* Cache object for getting or setting the compiled container's class name.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $compilationIndexCache;
/**
* PHP code storage object to use for the compiled container.
*
* @var \Drupal\Component\PhpStorage\PhpStorageInterface
*/
protected $storage;
/**
* Constructs a DrupalKernel object.
*
* @param string $environment
* 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 array $module_list
* (optional) The array of enabled modules as returned by module_list().
* @param Drupal\Core\Cache\CacheBackendInterface $compilation_index_cache
* (optional) If wanting to dump a compiled container to disk or use a
* previously compiled container, the cache object for the bin that stores
* the class name of the compiled container.
*/
public function __construct($environment, $debug, array $module_list = NULL, CacheBackendInterface $compilation_index_cache = NULL) {
parent::__construct($environment, $debug);
$this->compilationIndexCache = $compilation_index_cache;
$this->storage = drupal_php_storage('service_container');
if (isset($module_list)) {
$this->moduleList = $module_list;
}
else {
// @todo This is a temporary measure which will no longer be necessary
// once we have an ExtensionHandler for managing this list. See
// http://drupal.org/node/1331486.
$this->moduleList = module_list();
}
}
/**
* Overrides Kernel::init().
*/
......@@ -43,10 +100,7 @@ public function registerBundles() {
new CoreBundle(),
);
// @todo Remove the necessity of calling system_list() to find out which
// bundles exist. See http://drupal.org/node/1331486
$modules = array_keys(system_list('module_enabled'));
foreach ($modules as $module) {
foreach ($this->moduleList as $module) {
$camelized = ContainerBuilder::camelize($module);
$class = "Drupal\\{$module}\\{$camelized}Bundle";
if (class_exists($class)) {
......@@ -61,12 +115,43 @@ public function registerBundles() {
* Initializes the service container.
*/
protected function initializeContainer() {
// @todo We should be compiling the container and dumping to php so we don't
// have to recompile every time. There is a separate issue for this, see
// http://drupal.org/node/1668892.
$this->container = $this->buildContainer();
$this->container = NULL;
if ($this->compilationIndexCache) {
// The name of the compiled container class is generated from the hash of
// its contents and cached. This enables multiple compiled containers
// (for example, for different states of which modules are enabled) to
// exist simultaneously on disk and in memory.
if ($cache = $this->compilationIndexCache->get(implode(':', array('service_container', $this->environment, $this->debug)))) {
$class = $cache->data;
$cache_file = $class . '.php';
// First, try to load.
if (!class_exists($class, FALSE)) {
$this->storage->load($cache_file);
}
// If the load succeeded or the class already existed, use it.
if (class_exists($class, FALSE)) {
$fully_qualified_class_name = '\\' . $class;
$this->container = new $fully_qualified_class_name;
}
}
}
if (!isset($this->container)) {
$this->container = $this->buildContainer();
if ($this->compilationIndexCache && !$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';
}
}
$this->container->set('kernel', $this);
drupal_container($this->container);
if (isset($error)) {
watchdog('DrupalKernel', $error);
}
}
/**
......@@ -84,10 +169,7 @@ protected function buildContainer() {
foreach ($this->bundles as $bundle) {
$bundle->build($container);
}
// @todo Compile the container: http://drupal.org/node/1706064.
//$container->compile();
$container->compile();
return $container;
}
......@@ -100,6 +182,34 @@ 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 dumpContainer method in the parent class, but
* that method is reliant on the Config component which we do not use here.
*
* @param ContainerBuilder $container
* The service container.
* @param string $baseClass
* The name of the container's base class
*
* @return bool
* TRUE if the container was successfully dumped to disk.
*/
protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass) {
if (!$this->storage->writeable()) {
return FALSE;
}
// Cache the container.
$dumper = new PhpDumper($container);
$content = $dumper->dump(array('class' => 'DrupalServiceContainerStub', 'base_class' => $baseClass));
$class = 'c' . hash('sha256', $content);
$content = str_replace('DrupalServiceContainerStub', $class, $content);
$this->compilationIndexCache->set(implode(':', array('service_container', $this->environment, $this->debug)), $class);
return $this->storage->save($class . '.php', $content);
}
/**
* Overrides and eliminates this method from the parent class. Do not use.
*
......
......@@ -7,6 +7,7 @@
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Language\LanguageManager;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
......@@ -16,6 +17,23 @@
*/
class FinishResponseSubscriber implements EventSubscriberInterface {
/**
* The LanguageManager object for retrieving the correct language code.
*
* @var LanguageManager
*/
protected $languageManager;
/**
* Constructs a FinishResponseSubscriber object.
*
* @param LanguageManager $language_manager
* The LanguageManager object for retrieving the correct language code.
*/
public function __construct(LanguageManager $language_manager) {
$this->languageManager = $language_manager;
}
/**
* Sets extra headers on successful responses.
*
......@@ -30,10 +48,7 @@ public function onRespond(FilterResponseEvent $event) {
$response->headers->set('X-UA-Compatible', 'IE=edge,chrome=1', false);
// Set the Content-language header.
// @todo Receive the LanguageManager object as a constructor argument when
// the dependency injection container allows for it performantly:
// http://drupal.org/node/1706064.
$response->headers->set('Content-language', language(LANGUAGE_TYPE_INTERFACE)->langcode);
$response->headers->set('Content-language', $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE)->langcode);
// Because pages are highly dynamic, set the last-modified time to now
// since the page is in fact being regenerated right now.
......
......@@ -911,7 +911,7 @@ protected function rebuildContainer() {
// container in drupal_container(). Drupal\simpletest\TestBase::tearDown()
// restores the original container.
// @see Drupal\Core\DrupalKernel::initializeContainer()
$this->kernel = new DrupalKernel('testing', FALSE);
$this->kernel = new DrupalKernel('testing', FALSE, NULL);
// Booting the kernel is necessary to initialize the new DIC. While
// normally the kernel gets booted on demand in
// Symfony\Component\HttpKernel\handle(), this kernel needs manual booting
......
<?php
/**
* @file
* Contains Drupal\system\Tests\DrupalKernel\DrupalKernelTest.
*/
namespace Drupal\system\Tests\DrupalKernel;
use Drupal\Core\Cache\MemoryBackend;
use Drupal\Core\DrupalKernel;
use Drupal\simpletest\UnitTestBase;
use ReflectionClass;
/**
* Tests compilation of the DIC.
*/
class DrupalKernelTest extends UnitTestBase {
public static function getInfo() {
return array(
'name' => 'DrupalKernel tests',
'description' => 'Tests DIC compilation to disk.',
'group' => 'DrupalKernel',
);
}
/**
* Tests DIC compilation.
*/
function testCompileDIC() {
// Because we'll be instantiating a new kernel during this test, the
// container stored in drupal_container() will be updated as a side effect.
// We need to be able to restore it to the correct one at the end of this
// test.
$original_container = drupal_container();
global $conf;
$conf['php_storage']['service_container'] = array(
'class' => 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage',
'secret' => $GLOBALS['drupal_hash_salt'],
);
$cache = new MemoryBackend('test');
$module_enabled = array(
'system' => 'system',
'user' => 'user',
);
$kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache);
$kernel->boot();
// Instantiate it a second time and we should get the compiled Container
// class.
$kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache);
$kernel->boot();
$container = $kernel->getContainer();
$refClass = new ReflectionClass($container);
$is_compiled_container =
$refClass->getParentClass()->getName() == 'Symfony\Component\DependencyInjection\Container' &&
!$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$this->assertTrue($is_compiled_container);
// Reset the container.
drupal_container(NULL, TRUE);
// Now use the read-only storage implementation, simulating a "production"
// environment.
drupal_static_reset('drupal_php_storage');
$conf['php_storage']['service_container'] = array(
'class' => 'Drupal\Component\PhpStorage\FileReadOnlyStorage',
);
$kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache);
$kernel->boot();
$container = $kernel->getContainer();
$refClass = new ReflectionClass($container);
$is_compiled_container =
$refClass->getParentClass()->getName() == 'Symfony\Component\DependencyInjection\Container' &&
!$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$this->assertTrue($is_compiled_container);
// We make this assertion here purely to show that the new container below
// is functioning correctly, i.e. we get a brand new ContainerBuilder
// which has the required new services, after changing the list of enabled
// modules.
$this->assertFalse($container->has('bundle_test_class'));
// Reset the container.
drupal_container(NULL, TRUE);
// Add another module so that we can test that the new module's bundle is
// registered to the new container.
$module_enabled = array(
'system' => 'system',
'user' => 'user',
'bundle_test' => 'bundle_test',
);
$cache->flush();
$kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache);
$kernel->boot();
// Instantiate it a second time and we should still get a ContainerBuilder
// class because we are using the read-only PHP storage.
$kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache);
$kernel->boot();
$container = $kernel->getContainer();
$refClass = new ReflectionClass($container);
$is_container_builder = $refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$this->assertTrue($is_container_builder);
// Assert that the new module's bundle was registered to the new container.
$this->assertTrue($container->has('bundle_test_class'));
// Restore the original container.
drupal_container($original_container);
}
}
......@@ -23,9 +23,5 @@ public function build(ContainerBuilder $container) {