diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index c8ba628ac6c..815f7f3387a 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -35,6 +35,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\Routing\Route;
@@ -213,53 +214,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
* In case the host name in the request is not trusted.
*/
public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE) {
- // Include our bootstrap file.
- $core_root = dirname(dirname(dirname(__DIR__)));
- require_once $core_root . '/includes/bootstrap.inc';
- $class_loader_class = get_class($class_loader);
-
$kernel = new static($environment, $class_loader, $allow_dumping);
-
- // Ensure sane php environment variables..
static::bootEnvironment();
-
- // Get our most basic settings setup.
- $site_path = static::findSitePath($request);
- $kernel->setSitePath($site_path);
- Settings::initialize(dirname($core_root), $site_path, $class_loader);
-
- // Initialize our list of trusted HTTP Host headers to protect against
- // header attacks.
- $host_patterns = Settings::get('trusted_host_patterns', array());
- if (PHP_SAPI !== 'cli' && !empty($host_patterns)) {
- if (static::setupTrustedHosts($request, $host_patterns) === FALSE) {
- throw new BadRequestHttpException('The provided host name is not valid for this server.');
- }
- }
-
- // Redirect the user to the installation script if Drupal has not been
- // installed yet (i.e., if no $databases array has been defined in the
- // settings.php file) and we are not already installing.
- if (!Database::getConnectionInfo() && !drupal_installation_attempted() && PHP_SAPI !== 'cli') {
- $response = new RedirectResponse($request->getBasePath() . '/core/install.php');
- $response->prepare($request)->send();
- }
-
- // If the class loader is still the same, possibly upgrade to the APC class
- // loader.
- if ($class_loader_class == get_class($class_loader)
- && Settings::get('class_loader_auto_detect', TRUE)
- && function_exists('apc_fetch')) {
- $prefix = Settings::getApcuPrefix('class_loader', $core_root);
- $apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $class_loader);
- $class_loader->unregister();
- $apc_loader->register();
- $class_loader = $apc_loader;
- }
-
- // Ensure that the class loader reference is up-to-date.
- $kernel->classLoader = $class_loader;
-
+ $kernel->initializeSettings($request);
return $kernel;
}
@@ -379,6 +336,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
* {@inheritdoc}
*/
public function setSitePath($path) {
+ if ($this->booted) {
+ throw new \LogicException('Site path cannot be changed after calling boot()');
+ }
$this->sitePath = $path;
}
@@ -595,8 +555,77 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
* {@inheritdoc}
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
- $this->boot();
- return $this->getHttpKernel()->handle($request, $type, $catch);
+ // Ensure sane PHP environment variables.
+ static::bootEnvironment();
+
+ try {
+ $this->initializeSettings($request);
+
+ // Redirect the user to the installation script if Drupal has not been
+ // installed yet (i.e., if no $databases array has been defined in the
+ // settings.php file) and we are not already installing.
+ if (!Database::getConnectionInfo() && !drupal_installation_attempted() && PHP_SAPI !== 'cli') {
+ $response = new RedirectResponse($request->getBasePath() . '/core/install.php');
+ }
+ else {
+ $this->boot();
+ $response = $this->getHttpKernel()->handle($request, $type, $catch);
+ }
+ }
+ catch (\Exception $e) {
+ if ($catch === FALSE) {
+ throw $e;
+ }
+
+ $response = $this->handleException($e, $request, $type);
+ }
+
+ // Adapt response headers to the current request.
+ $response->prepare($request);
+
+ return $response;
+ }
+
+ /**
+ * Converts an exception into a response.
+ *
+ * @param \Exception $e
+ * An exception
+ * @param Request $request
+ * A Request instance
+ * @param int $type
+ * The type of the request (one of HttpKernelInterface::MASTER_REQUEST or
+ * HttpKernelInterface::SUB_REQUEST)
+ *
+ * @return Response
+ * A Response instance
+ */
+ protected function handleException(\Exception $e, $request, $type) {
+ if ($e instanceof HttpExceptionInterface) {
+ $response = new Response($e->getMessage(), $e->getStatusCode());
+ $response->headers->add($e->getHeaders());
+ return $response;
+ }
+ else {
+ // @todo: _drupal_log_error() and thus _drupal_exception_handler() prints
+ // the message directly. Extract a function which generates and returns it
+ // instead, then remove the output buffer hack here.
+ ob_start();
+ try {
+ // @todo: The exception handler prints the message directly. Extract a
+ // function which returns the message instead.
+ _drupal_exception_handler($e);
+ }
+ catch (\Exception $e) {
+ $message = Settings::get('rebuild_message', 'If you have just changed code (for example deployed a new module or moved an existing one) read https://www.drupal.org/documentation/rebuild');
+ if ($message && Settings::get('rebuild_access', FALSE)) {
+ $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
+ $message .= " or run the rebuild script";
+ }
+ print $message;
+ }
+ return new Response(ob_get_clean(), 500);
+ }
}
/**
@@ -796,6 +825,10 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
return;
}
+ // Include our bootstrap file.
+ $core_root = dirname(dirname(dirname(__DIR__)));
+ require_once $core_root . '/includes/bootstrap.inc';
+
// Enforce E_STRICT, but allow users to set levels not part of E_STRICT.
error_reporting(E_STRICT | E_ALL);
@@ -847,6 +880,43 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
static::$isEnvironmentInitialized = TRUE;
}
+ /**
+ * Locate site path and initialize settings singleton.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The current request.
+ *
+ * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+ * In case the host name in the request is not trusted.
+ */
+ protected function initializeSettings(Request $request) {
+ $site_path = static::findSitePath($request);
+ $this->setSitePath($site_path);
+ $class_loader_class = get_class($this->classLoader);
+ Settings::initialize($this->root, $site_path, $this->classLoader);
+
+ // Initialize our list of trusted HTTP Host headers to protect against
+ // header attacks.
+ $host_patterns = Settings::get('trusted_host_patterns', array());
+ if (PHP_SAPI !== 'cli' && !empty($host_patterns)) {
+ if (static::setupTrustedHosts($request, $host_patterns) === FALSE) {
+ throw new BadRequestHttpException('The provided host name is not valid for this server.');
+ }
+ }
+
+ // If the class loader is still the same, possibly upgrade to the APC class
+ // loader.
+ if ($class_loader_class == get_class($this->classLoader)
+ && Settings::get('class_loader_auto_detect', TRUE)
+ && function_exists('apc_fetch')) {
+ $prefix = Settings::getApcuPrefix('class_loader', $this->root);
+ $apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $this->classLoader);
+ $this->classLoader->unregister();
+ $apc_loader->register();
+ $this->classLoader = $apc_loader;
+ }
+ }
+
/**
* Bootstraps the legacy global request variables.
*
diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php
index ab17b77ff09..c7eb93f9d34 100644
--- a/core/lib/Drupal/Core/DrupalKernelInterface.php
+++ b/core/lib/Drupal/Core/DrupalKernelInterface.php
@@ -60,8 +60,11 @@ interface DrupalKernelInterface extends HttpKernelInterface {
/**
* Set the current site path.
*
- * @param $path
+ * @param string $path
* The current site path.
+ *
+ * @throws \LogicException
+ * In case the kernel is already booted.
*/
public function setSitePath($path);
diff --git a/core/lib/Drupal/Core/Test/TestKernel.php b/core/lib/Drupal/Core/Test/TestKernel.php
index bff37e6e5dc..55294caff8c 100644
--- a/core/lib/Drupal/Core/Test/TestKernel.php
+++ b/core/lib/Drupal/Core/Test/TestKernel.php
@@ -8,7 +8,6 @@
namespace Drupal\Core\Test;
use Drupal\Core\DrupalKernel;
-use Symfony\Component\HttpFoundation\Request;
/**
* Kernel to mock requests to test simpletest.
@@ -18,17 +17,17 @@ class TestKernel extends DrupalKernel {
/**
* {@inheritdoc}
*/
- public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE) {
+ public function __construct($environment, $class_loader, $allow_dumping = TRUE) {
// Include our bootstrap file.
require_once __DIR__ . '/../../../../includes/bootstrap.inc';
// Exit if we should be in a test environment but aren't.
if (!drupal_valid_test_ua()) {
- header($request->server->get('SERVER_PROTOCOL') . ' 403 Forbidden');
+ header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
exit;
}
- return parent::createFromRequest($request, $class_loader, $environment, $allow_dumping);
+ parent::__construct($environment, $class_loader, $allow_dumping);
}
}
diff --git a/core/lib/Drupal/Core/Test/TestRunnerKernel.php b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
index fa9fd51f20e..f19afb3bf92 100644
--- a/core/lib/Drupal/Core/Test/TestRunnerKernel.php
+++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
@@ -42,8 +42,8 @@ class TestRunnerKernel extends DrupalKernel {
'simpletest' => 0,
);
$this->moduleData = array(
- 'system' => new Extension(DRUPAL_ROOT, 'module', 'core/modules/system/system.info.yml', 'system.module'),
- 'simpletest' => new Extension(DRUPAL_ROOT, 'module', 'core/modules/simpletest/simpletest.info.yml', 'simpletest.module'),
+ 'system' => new Extension($this->root, 'module', 'core/modules/system/system.info.yml', 'system.module'),
+ 'simpletest' => new Extension($this->root, 'module', 'core/modules/simpletest/simpletest.info.yml', 'simpletest.module'),
);
}
diff --git a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
index ab6c224952d..84178a1f23b 100644
--- a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
+++ b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
@@ -166,7 +166,7 @@ class DrupalKernelTest extends KernelTestBase {
}
/**
- * Test repeated loading of compiled DIC with different environment.
+ * Tests repeated loading of compiled DIC with different environment.
*/
public function testRepeatedBootWithDifferentEnvironment() {
$request = Request::createFromGlobals();
@@ -189,4 +189,26 @@ class DrupalKernelTest extends KernelTestBase {
$this->pass('Repeatedly loaded compiled DIC with different environment');
}
+ /**
+ * Tests setting of site path after kernel boot.
+ */
+ public function testPreventChangeOfSitePath() {
+ // @todo: write a memory based storage backend for testing.
+ $modules_enabled = array(
+ 'system' => 'system',
+ 'user' => 'user',
+ );
+
+ $request = Request::createFromGlobals();
+ $kernel = $this->getTestKernel($request, $modules_enabled);
+ $pass = FALSE;
+ try {
+ $kernel->setSitePath('/dev/null');
+ }
+ catch (\LogicException $e) {
+ $pass = TRUE;
+ }
+ $this->assertTrue($pass, 'Throws LogicException if DrupalKernel::setSitePath() is called after boot');
+ }
+
}
diff --git a/core/modules/system/tests/http.php b/core/modules/system/tests/http.php
index 01856361eee..6d822ec88f0 100644
--- a/core/modules/system/tests/http.php
+++ b/core/modules/system/tests/http.php
@@ -20,10 +20,10 @@ foreach ($_SERVER as &$value) {
$value = str_replace('https://', 'http://', $value);
}
+$kernel = new TestKernel('testing', $autoloader, TRUE);
+
$request = Request::createFromGlobals();
-$kernel = TestKernel::createFromRequest($request, $autoloader, 'testing', TRUE);
-$response = $kernel
- ->handle($request)
- // Handle the response object.
- ->prepare($request)->send();
+$response = $kernel->handle($request);
+$response->send();
+
$kernel->terminate($request, $response);
diff --git a/core/modules/system/tests/https.php b/core/modules/system/tests/https.php
index 95ebed862ec..827dd7a197f 100644
--- a/core/modules/system/tests/https.php
+++ b/core/modules/system/tests/https.php
@@ -22,10 +22,10 @@ foreach ($_SERVER as &$value) {
$value = str_replace('http://', 'https://', $value);
}
+$kernel = new TestKernel('testing', $autoloader, TRUE);
+
$request = Request::createFromGlobals();
-$kernel = TestKernel::createFromRequest($request, $autoloader, 'testing', TRUE);
-$response = $kernel
- ->handle($request)
- // Handle the response object.
- ->prepare($request)->send();
+$response = $kernel->handle($request);
+$response->send();
+
$kernel->terminate($request, $response);
diff --git a/index.php b/index.php
index 654163fd549..750dc282dc2 100644
--- a/index.php
+++ b/index.php
@@ -9,36 +9,14 @@
*/
use Drupal\Core\DrupalKernel;
-use Drupal\Core\Site\Settings;
-use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
$autoloader = require_once 'autoload.php';
-try {
- $request = Request::createFromGlobals();
- $kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
- $response = $kernel
- ->handle($request)
- // Handle the response object.
- ->prepare($request)->send();
- $kernel->terminate($request, $response);
-}
-catch (HttpExceptionInterface $e) {
- $response = new Response($e->getMessage(), $e->getStatusCode());
- $response->prepare($request)->send();
-}
-catch (Exception $e) {
- $message = 'If you have just changed code (for example deployed a new module or moved an existing one) read https://www.drupal.org/documentation/rebuild';
- if (Settings::get('rebuild_access', FALSE)) {
- $rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
- $message .= " or run the rebuild script";
- }
+$kernel = new DrupalKernel('prod', $autoloader);
- // Set the response code manually. Otherwise, this response will default to a
- // 200.
- http_response_code(500);
- print $message;
- throw $e;
-}
+$request = Request::createFromGlobals();
+$response = $kernel->handle($request);
+$response->send();
+
+$kernel->terminate($request, $response);