diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 363cf12..f2b26d6 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -5,10 +5,17 @@ * Contains Drupal. */ +use Drupal\Core\DependencyInjection\PlaceholderContainer; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Url; /** + * Initialize \Drupal::$container with a placeholder object. + * See https://www.drupal.org/node/2363341 + */ +\Drupal::initStaticProperties(NULL); + +/** * Static Service Container wrapper. * * Generally, code in Drupal should accept its dependencies via either @@ -93,7 +100,7 @@ class Drupal { const CORE_MINIMUM_SCHEMA_VERSION = 8000; /** - * The currently active container object. + * The currently active container object, or a placeholder container. * * @var \Symfony\Component\DependencyInjection\ContainerInterface */ @@ -108,18 +115,46 @@ class Drupal { * environment does not leak into a test. */ public static function setContainer(ContainerInterface $container = NULL) { + if (!isset($container)) { + // @todo Remove the NULL case, and use unsetContainer() instead. + $container = new PlaceholderContainer('\Drupal::$container was unset with setContainer(NULL).'); + } static::$container = $container; } /** + * @param string|null $message + */ + public static function unsetContainer($message = NULL) { + if (!isset($message)) { + $message = '\Drupal::$container was unset with \Drupal::unsetContainer().'; + } + static::setContainer(new PlaceholderContainer($message)); + } + + /** + * Initializes the static properties. Called from within the class file. + */ + public static function initStaticProperties() { + $message = '\Drupal::$container is not initialized yet. \Drupal::setContainer() must be called with a real container.'; + static::setContainer(new PlaceholderContainer($message)); + } + + /** * Returns the currently active global container. * * @deprecated This method is only useful for the testing environment. It * should not be used otherwise. * - * @return \Symfony\Component\DependencyInjection\ContainerInterface + * @return \Symfony\Component\DependencyInjection\ContainerInterface|null */ public static function getContainer() { + if (static::$container instanceof PlaceholderContainer) { + // Currently, some components depend on this method returning NULL if not + // initialized. + // @todo Throw an exception instead, and change all code that expects NULL. + return NULL; + } return static::$container; } @@ -149,7 +184,7 @@ public static function service($id) { * TRUE if the specified service exists, FALSE otherwise. */ public static function hasService($id) { - return static::$container && static::$container->has($id); + return static::$container->has($id); } /** @@ -159,7 +194,8 @@ public static function hasService($id) { * TRUE if there is a currently active request object, FALSE otherwise. */ public static function hasRequest() { - return static::$container && static::$container->has('request_stack') && static::$container->get('request_stack')->getCurrentRequest() !== NULL; + return static::$container->has('request_stack') + && static::$container->get('request_stack')->getCurrentRequest() !== NULL; } /** @@ -469,6 +505,17 @@ public static function urlGenerator() { * the base path (like robots.txt) use Url::fromUri()->toString() with the * base:// scheme. * + * @param string $route_name + * The name of the route + * @param array $route_parameters + * An associative array of parameter names and values. + * @param array $options + * (optional) An associative array of additional options, + * + * @return string + * The generated URL for the given route. + * + * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() * @see \Drupal\Core\Url * @see \Drupal\Core\Url::fromRoute() * @see \Drupal\Core\Url::fromUri() @@ -493,8 +540,15 @@ public static function linkGenerator() { * generate() method. For detailed documentation, see * \Drupal\Core\Routing\LinkGeneratorInterface::generate(). * + * @param string $text + * The link text for the anchor tag as a translated string or render array. + * @param \Drupal\Core\Url $url + * The URL object used for the link, + * + * @return string + * An HTML string containing a link to the given route and parameters. + * * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate() - * @see \Drupal\Core\Url */ public static function l($text, Url $url) { return static::$container->get('link_generator')->generate($text, $url); diff --git a/core/lib/Drupal/Core/DependencyInjection/ContainerNotInitializedException.php b/core/lib/Drupal/Core/DependencyInjection/ContainerNotInitializedException.php new file mode 100644 index 0000000..6de8116 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerNotInitializedException.php @@ -0,0 +1,15 @@ +message = isset($exception_message) + ? $exception_message + : 'Container not initialized.'; + } + + /** + * {@inheritdoc} + */ + public function set($id, $service, $scope = self::SCOPE_CONTAINER) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * {@inheritdoc} + */ + public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * {@inheritdoc} + */ + public function has($id) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getParameter($name) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * {@inheritdoc} + */ + public function hasParameter($name) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * {@inheritdoc} + */ + public function setParameter($name, $value) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * {@inheritdoc} + */ + public function enterScope($name) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * {@inheritdoc} + */ + public function leaveScope($name) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * {@inheritdoc} + */ + public function addScope(ScopeInterface $scope) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * {@inheritdoc} + */ + public function hasScope($name) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * {@inheritdoc} + */ + public function isScopeActive($name) { + throw new ContainerNotInitializedException($this->message); + } + + /** + * Checks whether the given service has been initialized yet. + * + * This method is not part of Symfony's ContainerInterface, but it is needed + * for DrupalKernel::$container. + * + * @param string $id + * The service identifier + * + * @return bool + * TRUE, if the service has already been initialized. FALSE, otherwise + * @throws \Drupal\Core\DependencyInjection\ContainerNotInitializedException + * + * @see \Drupal\Core\DependencyInjection\Container::initialized() + * @see \Drupal\Core\DrupalKernel::$container + */ + public function initialized($id) { + throw new ContainerNotInitializedException($this->message); + } + +} diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index c014e4a..afab8c8 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -15,6 +15,7 @@ use Drupal\Core\Config\NullStorage; use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\DependencyInjection\PlaceholderContainer; use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\Core\DependencyInjection\YamlFileLoader; use Drupal\Core\Extension\ExtensionDiscovery; @@ -27,10 +28,8 @@ use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\TerminableInterface; -use Composer\Autoload\ClassLoader; /** * The DrupalKernel class is the core of Drupal itself. @@ -50,9 +49,20 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { const CONTAINER_BASE_CLASS = '\Drupal\Core\DependencyInjection\Container'; /** - * Holds the container instance. + * The service container. * - * @var \Symfony\Component\DependencyInjection\ContainerInterface + * It is assumed that the container implements $container->initialized($id), + * There is currently no interface to define this method, hence the + * alternative "|\Drupal\..\Container" in the type hint. + * + * @todo Create an interface with $container->initialized($id). + * + * Before its initialization in $this->initializeContainer(), $this->container + * will be filled with a PlaceholderContainer object. + * + * @see \Drupal\Core\DependencyInjection\PlaceholderContainer + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface|\Drupal\Core\DependencyInjection\Container */ protected $container; @@ -248,6 +258,7 @@ public function __construct($environment, $class_loader, $allow_dumping = TRUE) $this->environment = $environment; $this->classLoader = $class_loader; $this->allowDumping = $allow_dumping; + $this->container = new PlaceholderContainer('DrupalKernel::$container is not initialized yet.'); } /** @@ -404,7 +415,7 @@ public function shutdown() { } $this->container->get('stream_wrapper_manager')->unregister(); $this->booted = FALSE; - $this->container = NULL; + $this->container = new PlaceholderContainer('DrupalKernel::$container is not available after shutdown.'); $this->moduleList = NULL; $this->moduleData = array(); } @@ -413,6 +424,10 @@ public function shutdown() { * {@inheritdoc} */ public function getContainer() { + if ($this->container instanceof PlaceholderContainer) { + // @todo Throw an exception? + return NULL; + } return $this->container; } @@ -458,6 +473,7 @@ public function handlePageCache(Request $request) { $cache_enabled = TRUE; } else { + /** @var \Drupal\Core\Config\Config $config */ $config = $this->getContainer()->get('config.factory')->get('system.performance'); $cache_enabled = $config->get('cache.page.use_internal'); } @@ -550,8 +566,9 @@ public function terminate(Request $request, Response $response) { return; } - if ($this->getHttpKernel() instanceof TerminableInterface) { - $this->getHttpKernel()->terminate($request, $response); + $http_kernel = $this->getHttpKernel(); + if ($http_kernel instanceof TerminableInterface) { + $http_kernel->terminate($request, $response); } } @@ -596,16 +613,23 @@ protected function moduleData($module) { // If a module is within a profile directory but specifies another // profile for testing, it needs to be found in the parent profile. $settings = $this->getConfigStorage()->read('simpletest.settings'); - $parent_profile = !empty($settings['parent_profile']) ? $settings['parent_profile'] : NULL; + $parent_profile = !empty($settings['parent_profile']) + ? $settings['parent_profile'] + : NULL; + if ($parent_profile && !isset($profiles[$parent_profile])) { // In case both profile directories contain the same extension, the // actual profile always has precedence. $profiles = array($parent_profile => $all_profiles[$parent_profile]) + $profiles; } - $profile_directories = array_map(function ($profile) { - return $profile->getPath(); - }, $profiles); + $profile_directories = array_map( + function ($profile) { + /** @var \Drupal\Core\Extension\Extension $profile */ + return $profile->getPath(); + }, + $profiles); + $listing->setProfileDirectories($profile_directories); // Now find modules. @@ -619,6 +643,11 @@ protected function moduleData($module) { * * @todo Remove obsolete $module_list parameter. Only $module_filenames is * needed. + * + * @param array $module_list + * The new list of modules. + * @param array $module_filenames + * List of module filenames, keyed by module name. */ public function updateModules(array $module_list, array $module_filenames = array()) { $this->moduleList = $module_list; @@ -668,7 +697,7 @@ protected function getKernelParameters() { protected function initializeContainer($rebuild = FALSE) { $this->containerNeedsDumping = FALSE; $session_manager_started = FALSE; - if (isset($this->container)) { + if (!$this->container instanceof PlaceholderContainer) { // If there is a session manager, close and save the session. if ($this->container->initialized('session_manager')) { $session_manager = $this->container->get('session_manager'); @@ -703,6 +732,8 @@ protected function initializeContainer($rebuild = FALSE) { $this->attachSynthetic($container); + // From here forward, $this->container is definitely not a + // PlaceholderContainer anymore, but a "real" container. $this->container = $container; if ($session_manager_started) { $this->container->get('session_manager')->start(); @@ -928,6 +959,12 @@ protected function initializeCookieGlobals(Request $request) { /** * Returns service instances to persist from an old container to a new one. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface|\Drupal\Core\DependencyInjection\Container $container + * A service container that implements $container->initialized($id) + * + * @return object[] + * Array of services to persist, keyed by service id. */ protected function getServicesToPersist(ContainerInterface $container) { $persist = array(); @@ -942,6 +979,11 @@ protected function getServicesToPersist(ContainerInterface $container) { /** * Moves persistent service instances into a new container. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface|\Drupal\Core\DependencyInjection\Container $container + * A service container that implements $container->initialized($id) + * @param object[] $persist + * Array of services to persist, keyed by service id. */ protected function persistServices(ContainerInterface $container, array $persist) { foreach ($persist as $id => $object) { @@ -975,7 +1017,7 @@ public function rebuildContainer() { */ protected function attachSynthetic(ContainerInterface $container) { $persist = array(); - if (isset($this->container)) { + if (!$this->container instanceof PlaceholderContainer) { $persist = $this->getServicesToPersist($this->container); } $this->persistServices($container, $persist); diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index a5eb3b9..91ea20c 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -52,8 +52,8 @@ public function getServiceProviders($origin); /** * Gets the current container. * - * @return \Symfony\Component\DependencyInjection\ContainerInterface - * A ContainerInterface instance. + * @return \Symfony\Component\DependencyInjection\ContainerInterface|null + * A ContainerInterface instance, or NULL if not initialized yet. */ public function getContainer();