diff --git a/core/includes/common.inc b/core/includes/common.inc index 540f42b..4026136 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1,6 +1,7 @@ directories. - $profile = drupal_get_profile(); - // For SimpleTest to be able to test modules packaged together with a - // distribution we need to include the profile of the parent site (in which - // test runs are triggered). - if (drupal_valid_test_ua()) { - $testing_profile = config('simpletest.settings')->get('parent_profile'); - if ($testing_profile && $testing_profile != $profile) { - $searchdir[] = drupal_get_path('profile', $testing_profile) . '/' . $directory; - } - } - // In case both profile directories contain the same extension, the actual - // profile always has precedence. - $searchdir[] = drupal_get_path('profile', $profile) . '/' . $directory; - - // Always search for contributed and custom extensions in top-level - // directories as well as sites/all/* directories. If the same extension is - // located in both directories, then the latter wins for legacy/historical - // reasons. - $searchdir[] = $directory; - $searchdir[] = 'sites/all/' . $directory; - - if (file_exists("$config/$directory")) { - $searchdir[] = "$config/$directory"; - } - - // Get current list of items. - if (!function_exists('file_scan_directory')) { - require_once DRUPAL_ROOT . '/core/includes/file.inc'; - } - foreach ($searchdir as $dir) { - $files_to_add = file_scan_directory($dir, $mask, array('key' => $key, 'min_depth' => $min_depth)); - - // Duplicate files found in later search directories take precedence over - // earlier ones, so we want them to overwrite keys in our resulting - // $files array. - // The exception to this is if the later file is from a module or theme not - // compatible with Drupal core. This may occur during upgrades of Drupal - // core when new modules exist in core while older contrib modules with the - // same name exist in a directory such as /modules. - foreach (array_intersect_key($files_to_add, $files) as $file_key => $file) { - // If it has no info file, then we just behave liberally and accept the - // new resource on the list for merging. - if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info')) { - // Get the .info file for the module or theme this file belongs to. - $info = drupal_parse_info_file($info_file); - - // If the module or theme is incompatible with Drupal core, remove it - // from the array for the current search directory, so it is not - // overwritten when merged with the $files array. - if (isset($info['core']) && $info['core'] != DRUPAL_CORE_COMPATIBILITY) { - unset($files_to_add[$file_key]); - } - } - } - $files = array_merge($files, $files_to_add); - } - - return $files; + $listing = new SystemListingInfo(); + return $listing->scan($mask, $directory, $key, $min_depth); } /** diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 892571f..cc8318d 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -8,6 +8,7 @@ namespace Drupal\Core; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Config\FileStorage; use Drupal\Core\CoreBundle; use Drupal\Component\PhpStorage\PhpStorageInterface; use Symfony\Component\HttpKernel\Kernel; @@ -50,6 +51,18 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface { protected $storage; /** + * TRUE to attempt to use a dumped and stored container. + * + * @var bool + */ + protected $dump; + + /** + * @var array + */ + protected $bundleClasses; + + /** * Constructs a DrupalKernel object. * * @param string $environment @@ -62,24 +75,14 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface { * 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. + * @param bool $dump + * (optional) TRUE to attempt to use a dumped and stored container. */ - public function __construct($environment, $debug, array $module_list = NULL, CacheBackendInterface $compilation_index_cache = NULL) { + public function __construct($environment, $debug, array $module_list = NULL, $dump = FALSE) { 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(); - } + $this->moduleList = $module_list ?: array(); + $this->dump = $dump; } /** @@ -93,18 +96,59 @@ public function init() { } /** + * Overrides Kernel::boot(). + */ + public function boot() { + if ($this->booted) { + return; + } + // init container + $this->initializeContainer(); + $this->booted = TRUE; + } + + /** * Returns an array of available bundles. */ public function registerBundles() { $bundles = array( new CoreBundle(), ); + if (!$this->moduleList) { + $storage = new FileStorage(config_get_config_directory()); + $module_list = $storage->read('system.module'); + $module_list = $module_list['enabled']; + $this->moduleList = $module_list; + // Find filenames and prime the classloader. First, we need to add + // profiles because modules migh tbe inside those. + $profiles_scanner = new SystemListing(); + $all_profiles = $profiles_scanner ->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles'); + if (!empty($GLOBALS['drupal_test_info']['original_config_directory_active'])) { + $config_directory = $GLOBALS['drupal_test_info']['original_config_directory_active']; + if (empty($config_directory['absolute'])) { + $path = conf_path() . '/files/' . $config_directory['path']; + } + else { + $path = $config_directory['path']; + } + $parent_storage = new FileStorage($path); + $parent_modules = $parent_storage->read('system.module'); + $module_list += $parent_modules['enabled']; + } + $modules_scanner = new SystemListing(array_keys(array_intersect_key($module_list, $all_profiles))); + $module_data = $all_profiles + $modules_scanner ->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules'); + foreach ($module_list as $module => $weight) { + drupal_get_filename('module', $module, $module_data[$module]->uri); + drupal_classloader_register($module, dirname($module_data[$module]->uri)); + } + } - foreach ($this->moduleList as $module) { + foreach ($this->moduleList as $module => $data) { $camelized = ContainerBuilder::camelize($module); $class = "Drupal\\{$module}\\{$camelized}Bundle"; if (class_exists($class)) { $bundles[] = new $class(); + $this->bundleClasses[] = $class; } } return $bundles; @@ -125,34 +169,44 @@ public function updateModules($module_list) { } } + protected function getClassName() { + $parts = array('service_container', $this->environment, $this->debug); + if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) { + $parts[] = $GLOBALS['drupal_test_info']['test_run_id']; + } + if ($prefix = drupal_valid_test_ua()) { + $parts[] = $prefix; + } + return implode('_', $parts); + } + /** * Initializes the service container. */ protected function initializeContainer() { $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; - } + $class = $this->getClassName(); + $cache_file = $class . '.php'; + + // First, try to load. + if ($this->dump && !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)) { + $module_list = array_keys($this->moduleList ?: $this->container->get('config.factory')->get('system.module')->load()->get('enabled')); + if ($module_list !== $this->container->getParameter('container.modules')) { + unset($this->container); } } if (!isset($this->container)) { $this->container = $this->buildContainer(); - if ($this->compilationIndexCache && !$this->dumpDrupalContainer($this->container, $this->getContainerBaseClass())) { + if ($this->dump && !$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'; @@ -174,7 +228,11 @@ protected function initializeContainer() { * @return ContainerBuilder The compiled service container */ protected function buildContainer() { + // init bundles + $this->initializeBundles(); $container = $this->getContainerBuilder(); + $container->setParameter('container.bundles', $this->bundleClasses); + $container->setParameter('container.modules', array_keys($this->moduleList)); // Merge in the minimal bootstrap container. if ($bootstrap_container = drupal_container()) { @@ -216,11 +274,8 @@ protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass) } // 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); - + $class = $this->getClassName(); + $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass)); return $this->storage->save($class . '.php', $content); } @@ -236,5 +291,4 @@ protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass) */ public function registerContainerConfiguration(LoaderInterface $loader) { } - } diff --git a/core/lib/Drupal/Core/SystemListing.php b/core/lib/Drupal/Core/SystemListing.php new file mode 100644 index 0000000..de87456 --- /dev/null +++ b/core/lib/Drupal/Core/SystemListing.php @@ -0,0 +1,69 @@ +profiles = $profiles; + } + + function scan($mask, $directory, $key = 'name') { + if (!in_array($key, array('uri', 'filename', 'name'))) { + $key = 'uri'; + } + $config = conf_path(); + $files = array(); + + // Search for the directory in core. + $searchdir = array('core/' . $directory); + foreach ($this->profiles($directory) as $profile) { + $searchdir[] = $profile; + } + + // Always search for contributed and custom extensions in top-level + // directories as well as sites/all/* directories. If the same extension is + // located in both directories, then the latter wins for legacy/historical + // reasons. + $searchdir[] = $directory; + $searchdir[] = 'sites/all/' . $directory; + + if (file_exists("$config/$directory")) { + $searchdir[] = "$config/$directory"; + } + + // Get current list of items. + foreach ($searchdir as $dir) { + try { + $dir_iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS) + ); + } + catch (\Exception $e) { + continue; + } + $files_to_add = array(); + foreach ($dir_iterator as $file_object) { + $filename = $file_object->getFileName(); + if (preg_match($mask, $filename)) { + $file = new \stdClass(); + $file->uri = $file_object->getPathName(); + $file->filename = $file->uri; + $file->name = pathinfo($filename, PATHINFO_FILENAME); + $files_to_add[$file->$key] = $file; + } + } + $files = array_merge($files, $this->process($files, $files_to_add)); + } + return $files; + } + + protected function profiles($directory) { + return $this->profiles; + } + + protected function process($files, $files_to_add) { + return $files_to_add; + } +} + diff --git a/core/lib/Drupal/Core/SystemListingInfo.php b/core/lib/Drupal/Core/SystemListingInfo.php new file mode 100644 index 0000000..ac6dd74 --- /dev/null +++ b/core/lib/Drupal/Core/SystemListingInfo.php @@ -0,0 +1,54 @@ + directories. + $profile = drupal_get_profile(); + // For SimpleTest to be able to test modules packaged together with a + // distribution we need to include the profile of the parent site (in which + // test runs are triggered). + if (drupal_valid_test_ua()) { + $testing_profile = config('simpletest.settings')->get('parent_profile'); + if ($testing_profile && $testing_profile != $profile) { + $searchdir[] = drupal_get_path('profile', $testing_profile) . '/' . $directory; + } + } + // In case both profile directories contain the same extension, the actual + // profile always has precedence. + $searchdir[] = drupal_get_path('profile', $profile) . '/' . $directory; + return $searchdir; + } + protected function process($files, $files_to_add) { + // Duplicate files found in later search directories take precedence over + // earlier ones, so we want them to overwrite keys in our resulting + // $files array. + // The exception to this is if the later file is from a module or theme not + // compatible with Drupal core. This may occur during upgrades of Drupal + // core when new modules exist in core while older contrib modules with the + // same name exist in a directory such as /modules. + foreach (array_intersect_key($files_to_add, $files) as $file_key => $file) { + // If it has no info file, then we just behave liberally and accept the + // new resource on the list for merging. + if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info')) { + // Get the .info file for the module or theme this file belongs to. + $info = drupal_parse_info_file($info_file); + + // If the module or theme is incompatible with Drupal core, remove it + // from the array for the current search directory, so it is not + // overwritten when merged with the $files array. + if (isset($info['core']) && $info['core'] != DRUPAL_CORE_COMPATIBILITY) { + unset($files_to_add[$file_key]); + } + } + } + return $files_to_add; + } + +} diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index fdb819a..e1be71a 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -890,6 +890,7 @@ protected function prepareEnvironment() { $test_info = &$GLOBALS['drupal_test_info']; $test_info['test_run_id'] = $this->databasePrefix; $test_info['in_child_site'] = FALSE; + $test_info['config_directory_active'] = $this->originalConfigDirectories['active']; // Indicate the environment was set up correctly. $this->setupEnvironment = TRUE; diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index ebadf43..c194710 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -713,7 +713,17 @@ protected function setUp() { // Reset the static batch to remove Simpletest's batch operations. $batch = &batch_get(); $batch = array(); - + $file_variables = array( + 'file_public_path' => $this->public_files_directory, + 'file_private_path' => $this->private_files_directory, + 'file_temporary_path' => $this->temp_files_directory, + 'locale_translate_file_directory' => $this->translation_files_directory, + ); + // Make sure the installer writes to the proper directories. + foreach ($file_variables as $name => $value) { + $GLOBALS['conf'][$name] = $value; + } + drupal_static_reset('drupal_php_storage'); // Execute the non-interactive installer. require_once DRUPAL_ROOT . '/core/includes/install.core.inc'; install_drupal($settings); @@ -727,12 +737,10 @@ protected function setUp() { unset($conf['cache_classes']); unset($conf['lock_backend']); - // Set path variables. - variable_set('file_public_path', $this->public_files_directory); - variable_set('file_private_path', $this->private_files_directory); - variable_set('file_temporary_path', $this->temp_files_directory); - variable_set('locale_translate_file_directory', $this->translation_files_directory); - + // Persist path variables. + foreach ($file_variables as $name => $value) { + variable_set($name, $value); + } // Set 'parent_profile' of simpletest to add the parent profile's // search path to the child site's search paths. // @see drupal_system_listing() diff --git a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php index 41070c3..63c635d 100644 --- a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php @@ -7,7 +7,6 @@ namespace Drupal\system\Tests\DrupalKernel; -use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\DrupalKernel; use Drupal\simpletest\UnitTestBase; use ReflectionClass; @@ -39,16 +38,15 @@ function testCompileDIC() { '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 = new DrupalKernel('testing', FALSE, $module_enabled, TRUE); $kernel->boot(); // Instantiate it a second time and we should get the compiled Container // class. - $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache); + $kernel = new DrupalKernel('testing', FALSE, $module_enabled, TRUE); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new ReflectionClass($container); @@ -66,7 +64,7 @@ function testCompileDIC() { $conf['php_storage']['service_container'] = array( 'class' => 'Drupal\Component\PhpStorage\FileReadOnlyStorage', ); - $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache); + $kernel = new DrupalKernel('testing', FALSE, $module_enabled, TRUE); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new ReflectionClass($container); @@ -91,12 +89,11 @@ function testCompileDIC() { 'user' => 'user', 'bundle_test' => 'bundle_test', ); - $cache->flush(); - $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache); + $kernel = new DrupalKernel('testing', FALSE, $module_enabled, TRUE); $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 = new DrupalKernel('testing', FALSE, $module_enabled, TRUE); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new ReflectionClass($container); diff --git a/index.php b/index.php index a6eb61e..1c7c4b8 100644 --- a/index.php +++ b/index.php @@ -28,7 +28,7 @@ drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE); // @todo Figure out how best to handle the Kernel constructor parameters. -$kernel = new DrupalKernel('prod', FALSE, NULL, cache('bootstrap')); +$kernel = new DrupalKernel('prod', FALSE, NULL, TRUE); // Create a request object from the HTTPFoundation. $request = Request::createFromGlobals();