Issue #2389811 by znerol, mpdonadio, alexpott, hussainweb, neclimdul: Move all the logic out of index.php (again)

8.0.x
Alex Pott 2015-06-03 18:06:46 +01:00
parent 2f853b1863
commit 90d6fb15ed
8 changed files with 165 additions and 93 deletions

View File

@ -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 <a href="https://www.drupal.org/documentation/rebuild">https://www.drupal.org/documentation/rebuild</a>');
if ($message && Settings::get('rebuild_access', FALSE)) {
$rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
$message .= " or run the <a href=\"$rebuild_path\">rebuild script</a>";
}
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.
*

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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'),
);
}

View File

@ -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');
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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 <a href="https://www.drupal.org/documentation/rebuild">https://www.drupal.org/documentation/rebuild</a>';
if (Settings::get('rebuild_access', FALSE)) {
$rebuild_path = $GLOBALS['base_url'] . '/rebuild.php';
$message .= " or run the <a href=\"$rebuild_path\">rebuild script</a>";
}
$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);