diff --git a/core/includes/common.inc b/core/includes/common.inc index f1a7ce0..8ff4bfb 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -7,6 +7,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Database\Database; +use Drupal\Core\SystemListingInfo; use Drupal\Core\Template\Attribute; /** @@ -5101,140 +5102,16 @@ function drupal_cron_cleanup() { } /** - * Returns information about system object files (modules, themes, etc.). + * This function is kept only for backward compatibility. * - * This function is used to find all or some system object files (module files, - * theme files, etc.) that exist on the site. It searches in several locations, - * depending on what type of object you are looking for. For instance, if you - * are looking for modules and call: - * @code - * drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules'); - * @endcode - * this function will search: - * - the core modules directory; i.e., /core/modules - * - the testing profile directory; e.g., /profiles/testing/modules - * - your installation profile's directory; e.g., /profiles/standard/modules - * - the site-wide modules directory; i.e., /modules - * - the all-sites directory; i.e., /sites/all/modules - * - the site-specific directory; i.e., /sites/example.com/modules - * in that order, and return information about all of the files ending in - * .module in those directories. - * - * The information is returned in an associative array, which can be keyed on - * the file name ($key = 'filename'), the file name without the extension ($key - * = 'name'), or the full file stream URI ($key = 'uri'). If you use a key of - * 'filename' or 'name', files found later in the search will take precedence - * over files found earlier (unless they belong to a module or theme not - * compatible with Drupal core); if you choose a key of 'uri', you will get all - * files found. - * - * @param string $mask - * The preg_match() regular expression for the files to find. The expression - * must be anchored and use DRUPAL_PHP_FUNCTION_PATTERN for the file name part - * before the extension, since the results could contain matches that do not - * present valid Drupal extensions otherwise. - * @param string $directory - * The subdirectory name in which the files are found. For example, - * 'modules' will search all 'modules' directories and their sub-directories - * as explained above. - * @param string $key - * (optional) The key to be used for the associative array returned. Possible - * values are: - * - 'uri' for the file's URI. - * - 'filename' for the basename of the file. - * - 'name' for the name of the file without the extension. - * For 'name' and 'filename' only the highest-precedence file is returned. - * Defaults to 'name'. - * @param int $min_depth - * (optional) Minimum depth of directories to return files from, relative to - * each directory searched. For instance, a directory of 'modules' and a - * minimum depth of 2 would find modules inside /modules/node/tests, but not - * modules directly in /modules/node. Defaults to 1. The default is sufficient - * and suitable for all extension types known by Drupal core. - * - * @return array - * An associative array of file objects, keyed on the chosen key. Each element - * in the array is an object containing file information, with properties: - * - 'uri': Full URI of the file. - * - 'filename': File name. - * - 'name': Name of file without the extension. + * @see \Drupal\Core\SystemListing::scan(). */ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) { - $config = conf_path(); - $files = array(); - - // Search for the directory in core. - $searchdir = array('core/' . $directory); - - // The 'core/profiles' directory contains pristine collections of modules and - // themes as provided by a distribution. It is pristine in the same way that - // the 'core/modules' directory is pristine for core; users should avoid - // any modification by using the top-level or sites/ 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, - // Do not recurse into ./lib directories; they cannot contain extensions. - // We also skip templates, css, and js directories. - // @todo Find a way to skip ./config directories (but not modules/config). - 'nomask' => '/^(CVS|lib|templates|css|js)$/', - )); - - // 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 = DRUPAL_ROOT . '/' . 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; + // As SystemListing is required to build a dependency injection container + // from scratch and SystemListingInfo only extends SystemLising, this + // class needs to be hardwired. + $listing = new SystemListingInfo(); + return $listing->scan($mask, $directory, $key, $min_depth); } /** diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 2b117bc..d1c0bac 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1504,7 +1504,7 @@ function install_bootstrap_full(&$install_state) { module_list_reset(); // Instantiate the kernel. - $kernel = new DrupalKernel('prod', FALSE, NULL); + $kernel = new DrupalKernel('prod', FALSE, drupal_classloader()); $kernel->boot(); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); } diff --git a/core/includes/module.inc b/core/includes/module.inc index 0c1a30b..a711cf1 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -507,7 +507,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) { // @todo The if statement is here because install_begin_request() creates // a container without a kernel. It probably shouldn't. if ($kernel = drupal_container()->get('kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE)) { - $kernel->updateModules(module_list()); + $kernel->updateModules(module_list(), array($module => drupal_get_path('module', $module))); } // Refresh the schema to include it. drupal_get_schema(NULL, TRUE); diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 892571f..109ada4 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -8,6 +8,8 @@ namespace Drupal\Core; use Drupal\Core\Cache\CacheBackendInterface; +use Symfony\Component\ClassLoader\UniversalClassLoader; +use Drupal\Core\Config\FileStorage; use Drupal\Core\CoreBundle; use Drupal\Component\PhpStorage\PhpStorageInterface; use Symfony\Component\HttpKernel\Kernel; @@ -32,15 +34,29 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface { * Holds the list of enabled modules. * * @var array + * An associative array whose keys are module names and whose values are + * ignored. */ protected $moduleList; /** - * Cache object for getting or setting the compiled container's class name. + * Holds an updated list of enabled modules. * - * @var \Drupal\Core\Cache\CacheBackendInterface + * @var array + * An associative array whose keys are module names and whose values are + * ignored. + */ + protected $newModuleList; + + /** + * An array of module data objects. + * + * The data objects have the same datastructure as returned by + * file_scan_directory() but only the uri property is used. + * + * @var array */ - protected $compilationIndexCache; + protected $moduleData = array(); /** * PHP code storage object to use for the compiled container. @@ -50,6 +66,20 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface { protected $storage; /** + * The classloader object. + * + * @var \Symfony\Component\ClassLoader\UniversalClassLoader + */ + protected $classLoader; + + /** + * The list of the classnames of the bundles in this kernel. + * + * @var array + */ + protected $bundleClasses; + + /** * Constructs a DrupalKernel object. * * @param string $environment @@ -60,26 +90,20 @@ class DrupalKernel extends Kernel implements DrupalKernelInterface { * 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) { + * @param \Symfony\Component\ClassLoader\UniversalClassLoader $class_loader + * (optional) The classloader is only used if $storage is not given or + * the load from storage fails and a container rebuild is required. In + * this case, the loaded modules will be registered with this loader in + * order to be able to find the module bundles. + * @param \Drupal\Component\PhpStorage\PhpStorageInterface $storage + * (optional) An object handling the load and save of the compiled + * container. If not specified, the container will neither be stored to + * disk nor read from there. + */ + public function __construct($environment, $debug, UniversalClassLoader $class_loader, PhpStorageInterface $storage = 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(); - } + $this->storage = $storage; + $this->classLoader = $class_loader; } /** @@ -93,28 +117,89 @@ public function init() { } /** + * Overrides Kernel::boot(). + */ + public function boot() { + if ($this->booted) { + return; + } + $this->initializeContainer(); + $this->booted = TRUE; + } + + /** * Returns an array of available bundles. */ public function registerBundles() { $bundles = array( new CoreBundle(), ); + if (!isset($this->moduleList)) { + $storage = new FileStorage(config_get_config_directory()); + $module_list = $storage->read('system.module'); + $this->moduleList = isset($module_list['enabled']) ? $module_list['enabled'] : array(); + } - foreach ($this->moduleList as $module) { + $namespaces = $this->classLoader->getNamespaces(); + foreach ($this->moduleList as $module => $weight) { + // When installing new modules, the modules in the list passed to + // updateModules() do not yet have their namespace registered. + $namespace = 'Drupal\\' . $module; + if (!isset($namespaces[$namespace]) && $this->moduleData($module)) { + $this->classLoader->registerNamespace($namespace, dirname(DRUPAL_ROOT . '/' . $this->moduleData($module)->uri) . '/lib'); + } $camelized = ContainerBuilder::camelize($module); $class = "Drupal\\{$module}\\{$camelized}Bundle"; if (class_exists($class)) { $bundles[] = new $class(); + $this->bundleClasses[] = $class; } } return $bundles; } /** + * Returns module data on the filesystem. + * + * @param $module + * The name of the module. + * + * @return \stdClass|bool + * Returns a stdClass object if the module data is found containing at + * least an uri property with the module path, for example + * core/modules/user/user.module. + */ + protected function moduleData($module) { + if (!$this->moduleData) { + // Find filenames to prime the classloader. First, find profiles. + // Profiles might want to add a bundle too and they also can contain + // modules. + $profiles_scanner = new SystemListing(); + $all_profiles = $profiles_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles'); + $profiles = array_keys(array_intersect_key($this->moduleList, $all_profiles)); + $storage = new FileStorage(config_get_config_directory()); + // If a module is within a profile directory but specifies another + // profile for testing, it needs to be found in the parent profile. + if (($parent_profile_config = $storage->read('simpletest.settings')) && isset($parent_profile_config['parent_profile']) && $parent_profile_config['parent_profile'] != $profiles[0]) { + // In case both profile directories contain the same extension, the + // actual profile always has precedence. + array_unshift($profiles, $parent_profile_config['parent_profile']); + } + // Now find modules. + $modules_scanner = new SystemListing($profiles); + $this->moduleData = $all_profiles + $modules_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules'); + } + return isset($this->moduleData[$module]) ? $this->moduleData[$module] : FALSE; + } + + /** * Implements Drupal\Core\DrupalKernelInterface::updateModules(). */ - public function updateModules($module_list) { - $this->moduleList = $module_list; + public function updateModules(array $module_list, array $module_paths = array()) { + $this->newModuleList = $module_list; + foreach ($module_paths as $module => $path) { + $this->moduleData[$module] = (object) array('uri' => $path); + } // If we haven't yet booted, we don't need to do anything: the new module // list will take effect when boot() is called. If we have already booted, // then reboot in order to refresh the bundle list and container. @@ -126,33 +211,61 @@ public function updateModules($module_list) { } /** + * Returns the classname based on environment, debug and testing prefix. + * + * @return string + * The class name. + */ + protected function getClassName() { + $parts = array('service_container', $this->environment, $this->debug); + // Make sure to use a testing-specific container even in the parent site. + if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) { + $parts[] = $GLOBALS['drupal_test_info']['test_run_id']; + } + elseif ($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; - } + + if ($this->storage) { + $class = $this->getClassName(); + $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->moduleList) && isset($this->newModuleList) && array_keys($this->moduleList) !== array_keys($this->newModuleList)) { + unset($this->container); + $this->moduleList = $this->newModuleList; + unset($this->newModuleList); + } + // Verify that the module list didn't change since the container was + // compiled. + elseif (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->storage && !$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 +287,10 @@ protected function initializeContainer() { * @return ContainerBuilder The compiled service container */ protected function buildContainer() { + $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 +332,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); } diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index 828f3ba..d4c8b8b 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -25,6 +25,8 @@ * * @param array $module_list * The new list of modules. + * @param array $module_path + * List of module paths, keyed by module name. */ - public function updateModules($module_list); + public function updateModules(array $module_list, array $module_path = array()); } diff --git a/core/lib/Drupal/Core/SystemListing.php b/core/lib/Drupal/Core/SystemListing.php new file mode 100644 index 0000000..6390f2b --- /dev/null +++ b/core/lib/Drupal/Core/SystemListing.php @@ -0,0 +1,198 @@ +profiles = $profiles; + } + + /** + * Returns information about system object files (modules, themes, etc.). + * + * This function is used to find all or some system object files (module + * files, theme files, etc.) that exist on the site. It searches in several + * locations, depending on what type of object you are looking for. For + * instance, if you are looking for modules and call: + * @code + * $scanner = new SystemListing(); + * $all_profiles = $profiles_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles'); + * @endcode + * this function will search: + * - the core modules directory; i.e., /core/modules + * - the profiles directories as defined by the profiles() method. + * - the site-wide modules directory; i.e., /modules + * - the all-sites directory; i.e., /sites/all/modules + * - the site-specific directory; i.e., /sites/example.com/modules + * in that order, and return information about all of the files ending in + * .module in those directories. + * + * The information is returned in an associative array, which can be keyed + * on the file name ($key = 'filename'), the file name without the extension + * ($key = 'name'), or the full file stream URI ($key = 'uri'). If you use a + * key of 'filename' or 'name', files found later in the search will take + * precedence over files found earlier (unless they belong to a module or + * theme not compatible with Drupal core); if you choose a key of 'uri', + * you will get all files found. + * + * @param string $mask + * The preg_match() regular expression for the files to find. The + * expression must be anchored and use DRUPAL_PHP_FUNCTION_PATTERN for the + * file name part before the extension, since the results could contain + * matches that do not present valid Drupal extensions otherwise. + * @param string $directory + * The subdirectory name in which the files are found. For example, + * 'modules' will search all 'modules' directories and their + * sub-directories as explained above. + * @param string $key + * (optional) The key to be used for the associative array returned. + * Possible values are: + * - 'uri' for the file's URI. + * - 'filename' for the basename of the file. + * - 'name' for the name of the file without the extension. + * For 'name' and 'filename' only the highest-precedence file is returned. + * Defaults to 'name'. + * + * @return array + * An associative array of file objects, keyed on the chosen key. Each + * element in the array is an object containing file information, with + * properties: + * - 'uri': Full URI of the file. + * - 'filename': File name. + * - 'name': Name of file without the extension. + */ + 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"; + } + // @todo Find a way to skip ./config directories (but not modules/config). + $nomask = '/^(CVS|lib|templates|css|js)$/'; + $files = array(); + // Get current list of items. + foreach ($searchdir as $dir) { + $files = array_merge($files, $this->process($files, $this->scanDirectory($dir, $key, $mask, $nomask))); + } + return $files; + } + + /** + * List the profiles for this directory. + * + * This version only returns those passed to the constructor. + * + * @param string $directory + * The current search directory like 'modules' or 'themes'. + * + * @return array + * A list of profiles. + */ + protected function profiles($directory) { + return $this->profiles; + } + + /** + * Process the files to add before adding them. + * + * @param array $files + * Every file found so far. + * @param array $files_to_add + * The files found in a single directory. + * + * @return array + * The processed list of file objects. For example, the SystemListingInfo + * class removes files not compatible with the current core version. + */ + protected function process(array $files, array $files_to_add) { + return $files_to_add; + } + + /** + * Abbreviated version of file_scan_directory(). + * + * @param $dir + * The base directory or URI to scan, without trailing slash. + * @param $key + * The key to be used for the returned associative array of files. + * Possible values are 'uri', for the file's URI; 'filename', for the + * basename of the file; and 'name' for the name of the file without the + * extension. + * @param $mask + * The preg_match() regular expression of the files to find. + * @param $nomask + * The preg_match() regular expression of the files to ignore. + * + * @return array + * An associative array (keyed on the chosen key) of objects with 'uri', + * 'filename', and 'name' members corresponding to the matching files. + */ + protected function scanDirectory($dir, $key, $mask, $nomask) { + $files = array(); + if (is_dir($dir)) { + // Avoid warnings when opendir does not have the permissions to open a + // directory. + if ($handle = @opendir($dir)) { + while (FALSE !== ($filename = readdir($handle))) { + // Skip this file if it matches the nomask or starts with a dot. + if ($filename[0] != '.' && !preg_match($nomask, $filename)) { + $uri = "$dir/$filename"; + if (is_dir($uri)) { + // Give priority to files in this folder by merging them in after + // any subdirectory files. + $files = array_merge($this->scanDirectory($uri, $key, $mask, $nomask), $files); + } + elseif (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); + $files[$file->$key] = $file; + } + } + } + closedir($handle); + } + } + return $files; + } +} diff --git a/core/lib/Drupal/Core/SystemListingInfo.php b/core/lib/Drupal/Core/SystemListingInfo.php new file mode 100644 index 0000000..8d45e1d --- /dev/null +++ b/core/lib/Drupal/Core/SystemListingInfo.php @@ -0,0 +1,72 @@ + + // 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; + } + + /** + * Overrides Drupal\Core\SystemListing::process(). + */ + protected function process(array $files, array $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..27333e8 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -912,7 +912,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, NULL); + $this->kernel = new DrupalKernel('testing', FALSE, drupal_classloader()); // 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 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..f4dd294 100644 --- a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php @@ -7,8 +7,9 @@ namespace Drupal\system\Tests\DrupalKernel; -use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\DrupalKernel; +use Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage; +use Drupal\Component\PhpStorage\FileReadOnlyStorage; use Drupal\simpletest\UnitTestBase; use ReflectionClass; @@ -34,21 +35,25 @@ function testCompileDIC() { // 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', + $classloader = drupal_classloader(); + $configuration = array( + 'bin' => 'service_container', + 'directory' => DRUPAL_ROOT . '/' . variable_get('file_public_path', conf_path() . '/files') . '/php', 'secret' => $GLOBALS['drupal_hash_salt'], ); - $cache = new MemoryBackend('test'); + // @todo: write a memory based storage backend for testing. + $php_storage = new MTimeProtectedFastFileStorage($configuration); $module_enabled = array( 'system' => 'system', 'user' => 'user', ); - $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache); + $kernel = new DrupalKernel('testing', FALSE, $classloader, $php_storage); + $kernel->updateModules($module_enabled); $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, $classloader, $php_storage); + $kernel->updateModules($module_enabled); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new ReflectionClass($container); @@ -62,11 +67,9 @@ function testCompileDIC() { // 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); + $php_storage = new FileReadOnlyStorage($configuration); + $kernel = new DrupalKernel('testing', FALSE, $classloader, $php_storage); + $kernel->updateModules($module_enabled); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new ReflectionClass($container); @@ -86,17 +89,14 @@ function testCompileDIC() { // 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); + $module_enabled['bundle_test'] = 'bundle_test'; + $kernel = new DrupalKernel('testing', FALSE, $classloader, $php_storage); + $kernel->updateModules($module_enabled); $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, $classloader, $php_storage); + $kernel->updateModules($module_enabled); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new ReflectionClass($container); diff --git a/core/modules/system/tests/http.php b/core/modules/system/tests/http.php index 297e3c7..8594648 100644 --- a/core/modules/system/tests/http.php +++ b/core/modules/system/tests/http.php @@ -36,6 +36,6 @@ drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE); -$kernel = new DrupalKernel('prod', FALSE); +$kernel = new DrupalKernel('prod', FALSE, drupal_classloader()); $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 8e09a5d..54d8878 100644 --- a/core/modules/system/tests/https.php +++ b/core/modules/system/tests/https.php @@ -35,6 +35,6 @@ drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE); -$kernel = new DrupalKernel('prod', FALSE); +$kernel = new DrupalKernel('prod', FALSE, drupal_classloader()); $response = $kernel->handle($request)->prepare($request)->send(); $kernel->terminate($request, $response); diff --git a/index.php b/index.php index a6eb61e..4d56290 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, drupal_classloader(), drupal_php_storage('service_container')); // Create a request object from the HTTPFoundation. $request = Request::createFromGlobals();