diff --git a/Bridges/DrupalKernel.php b/Bridges/DrupalKernel.php index a690123..0c80b9a 100644 --- a/Bridges/DrupalKernel.php +++ b/Bridges/DrupalKernel.php @@ -1,155 +1,173 @@ application) { - return; - } - - $content = ''; - $headers = $request->getHeaders(); - $contentLength = isset($headers['Content-Length']) ? (int) $headers['Content-Length'] : 0; - - $request->on('data', function($data) - use ($request, $response, &$content, $contentLength) - { - // read data (may be empty for GET request) - $content .= $data; - - // handle request after receive - if (strlen($content) >= $contentLength) { - $syRequest = self::mapRequest($request, $content); - - try { - $syResponse = $this->application->handle($syRequest); - } catch (\Exception $exception) { - $response->writeHead(500); // internal server error - $response->end(); - return; - } - - self::mapResponse($response, $syResponse); - - if ($this->application instanceof TerminableInterface) { - $this->application->terminate($syRequest, $syResponse); - } - } - }); +/** + * PHP-PM bridge adapter for DrupalKernel. + * + * Extends `\PHPPM\Bridges\HttpKernel` to populate various request + * meta-variables specified by CGI/1.1 (RFC 3875). + * + * @see http://www.faqs.org/rfcs/rfc3875.html + * @see http://php.net/manual/en/reserved.variables.server.php + */ +class DrupalKernel extends SymfonyBridge implements BridgeInterface { + + /** + * {@inheritdoc} + */ + public function onRequest(ReactRequest $request, ReactResponse $response) { + + if (NULL === $this->application) { + return; } - /** - * Convert React\Http\Request to Symfony\Component\HttpFoundation\Request - * - * @param ReactRequest $reactRequest - * @return SymfonyRequest $syRequest - */ - protected static function mapRequest(ReactRequest $reactRequest, $content) - { - $method = $reactRequest->getMethod(); - $headers = $reactRequest->getHeaders(); - $query = $reactRequest->getQuery(); - $post = array(); - - // parse body? - if (isset($headers['Content-Type']) && (0 === strpos($headers['Content-Type'], 'application/x-www-form-urlencoded')) - && in_array(strtoupper($method), array('POST', 'PUT', 'DELETE', 'PATCH')) - ) { - parse_str($content, $post); - } + $content = ''; + $headers = $request->getHeaders(); + $contentLength = isset($headers['Content-Length']) ? (int) $headers['Content-Length'] : 0; - $cookies = array(); - if (isset($headers['Cookie'])) { - $headersCookie = explode(';', $headers['Cookie']); - foreach ($headersCookie as $cookie) { - list($name, $value) = explode('=', trim($cookie)); - $cookies[$name] = $value; - } - } + $request->on('data', function($data) + use ($request, $response, &$content, $contentLength) { - $parameters = - in_array(strtoupper($method), array('POST', 'PUT', 'DELETE', 'PATCH')) - ? $post - : $query; - $syRequest = SymfonyRequest::create( - // $uri, $method, $parameters, $cookies, $files, $server, $content - $reactRequest->getPath(), - $method, - $parameters, - $cookies, - array(), - array(), - $content - ); - $syRequest->headers->replace($headers); - - // Set CGI/1.1 (RFC 3875) server vars. - // @see http://php.net/manual/en/reserved.variables.server.php - // @see http://www.faqs.org/rfcs/rfc3875.html - $serverVars = array_merge( - $syRequest->server->all(), - array( - 'DOCUMENT_ROOT' => $_SERVER['DOCUMENT_ROOT'], - 'GATEWAY_INTERFACE' => 'CGI/1.1', - 'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'], - // SCRIPT_FILENAME contains the name of the php-pm startup script. - // Must override here. - 'SCRIPT_FILENAME' => $_SERVER['DOCUMENT_ROOT'] . $_SERVER['SCRIPT_NAME'], - ) - ); - $syRequest->server->replace($serverVars); - - return $syRequest; - } + // Read data (may be empty for GET request). + $content .= $data; + // Handle request after receive. + if (strlen($content) >= $contentLength) { + $syRequest = self::mapRequest($request, $content); - /** - * Convert Symfony\Component\HttpFoundation\Response to React\Http\Response - * - * @param ReactResponse $reactResponse - * @param SymfonyResponse $syResponse - */ - protected static function mapResponse(ReactResponse $reactResponse, - SymfonyResponse $syResponse) - { - $headers = $syResponse->headers->all(); - $reactResponse->writeHead($syResponse->getStatusCode(), $headers); - - // @TODO convert StreamedResponse in an async manner - if ($syResponse instanceof SymfonyStreamedResponse) { - ob_start(); - $syResponse->sendContent(); - $content = ob_get_contents(); - ob_end_clean(); + try { + $syResponse = $this->application->handle($syRequest); + } + catch (\Exception $exception) { + // Internal server error. + $response->writeHead(500); + $response->end(); + return; } - else { - $content = $syResponse->getContent(); + + self::mapResponse($response, $syResponse); + + if ($this->application instanceof TerminableInterface) { + $this->application->terminate($syRequest, $syResponse); } + } + }); + } + + /** + * {@inheritdoc} + */ + protected static function mapRequest(ReactRequest $reactRequest, $content) { + + $method = strtoupper($reactRequest->getMethod()); + $headers = $reactRequest->getHeaders(); + $query = $reactRequest->getQuery(); + $post = array(); + + $requestIsPostType = in_array( + $method, + array('POST', 'PUT', 'DELETE', 'PATCH') + ); + + // Parse body? + if (isset($headers['Content-Type']) && (0 === strpos($headers['Content-Type'], 'application/x-www-form-urlencoded')) + && $requestIsPostType + ) { + parse_str($content, $post); + } + + $cookies = array(); + if (isset($headers['Cookie'])) { + $headersCookie = explode(';', $headers['Cookie']); + foreach ($headersCookie as $cookie) { + list($name, $value) = explode('=', trim($cookie)); + $cookies[$name] = $value; + } + } - $reactResponse->end($content); + // Add any query string to URI so SymfonyRequest::create() can access it. + $uri = $reactRequest->getPath() . + (empty($query) ? '' : '?' . http_build_query($query)); + + // SymfonyRequest::create() expects $parameters to contain either + // $_GET or $_POST. + $parameters = $requestIsPostType ? $post : $query; + + $syRequest = SymfonyRequest::create( + // $uri, $method, $parameters, $cookies, $files, $server, $content. + $uri, + $method, + $parameters, + $cookies, + array(), + array(), + $content + ); + $syRequest->headers->replace($headers); + + // Set CGI/1.1 (RFC 3875) server vars. + if (empty($_ENV)) { + // In some cases with cli, $_ENV isn't set, so get with getenv(). + // @see http://stackoverflow.com/questions/8798294/getenv-vs-env-in-php/21473853#21473853 + // @todo: Make this more efficient to eliminate running per request. + // Static variable? + $_ENV['DOCUMENT_ROOT'] = getenv('DOCUMENT_ROOT'); + $_ENV['SCRIPT_NAME'] = getenv('SCRIPT_NAME'); + } + $serverVars = array_merge( + $syRequest->server->all(), + array( + 'DOCUMENT_ROOT' => $_ENV['DOCUMENT_ROOT'], + 'GATEWAY_INTERFACE' => 'CGI/1.1', + 'SCRIPT_NAME' => $_ENV['SCRIPT_NAME'], + // SCRIPT_FILENAME contains the name of the php-pm startup script. + // Must override here. + 'SCRIPT_FILENAME' => $_ENV['DOCUMENT_ROOT'] . $_ENV['SCRIPT_NAME'], + ) + ); + $syRequest->server->replace($serverVars); + + return $syRequest; + } + + + /** + * {@inheritdoc} + */ + protected static function mapResponse(ReactResponse $reactResponse, + SymfonyResponse $syResponse) { + + $headers = $syResponse->headers->all(); + $reactResponse->writeHead($syResponse->getStatusCode(), $headers); + + // @TODO convert StreamedResponse in an async manner + if ($syResponse instanceof SymfonyStreamedResponse) { + ob_start(); + $syResponse->sendContent(); + $content = ob_get_contents(); + ob_end_clean(); } + else { + $content = $syResponse->getContent(); + } + + $reactResponse->end($content); + } } diff --git a/README.md b/README.md index 9d5978e..9af34c2 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,25 @@ -# PHP-PM HttpKernel Adapter +# PHP-PM DrupalKernel Adapter + +## Overview This is a fork of PHP-PM's HttpKernel adapter for integrating Drupal with PHP-PM (therefore, also with ReactPHP). +The primary components are a bootstrap and bridge. + See: * https://github.com/php-pm/php-pm -* https://github.com/php-pm/php-pm-httpkernel. +* https://github.com/php-pm/php-pm-httpkernel +* http://marcjschmidt.de/blog/2014/02/08/php-high-performance.html The code is in alpha -- very experimental. Last tested against `drupal-8.0.2`. -View / report issues at https://github.com/kentr/php-pm-drupal/issues. +View / report issues at https://github.com/php-pm/php-pm-drupal/issues. -### Setup +### Setup / Usage 1. Install Drupal. - 2. From the Drupal web root, install this project with composer: `composer require kentr/php-pm-drupal-adapter`. + 2. From the Drupal web root, install this project with composer: `composer require php-pm/drupal-adapter`. This will also install PHP-PM and the default React <-> Symfony bridge (php-pm/httpkernel-adapter). @@ -40,3 +45,32 @@ start \ --bridge=httpKernel \ --bootstrap=PHPPM\\Bootstraps\\Drupal ``` + +## DrupalKernel bridge +By default, PHP-PM uses the `\PHPPM\Bridges\HttpKernel` bridge to convert a ReactPHP request into a Symfony request and the Symfony response into a ReactPHP response. + +The included `\PHPPM\Bridges\DrupalKernel` bridge extends `\PHPPM\Bridges\HttpKernel` to populate various request meta-variables specified by [CGI/1.1 (RFC 3875)](http://www.faqs.org/rfcs/rfc3875.html). + +### Setup / Usage + + 1. Install as described above. + + 2. Include the environment variables and the `--bridge` option in the php-pm start command. + + Supported environment variables: + * **SCRIPT_NAME:** '/index.php' to emulate a standard setup where web requests execute Drupal's `index.php` script. + * **SERVER_NAME:** Your site's server / domain name. If you're using trusted host settings (`$settings['trusted_host_patterns']` in `settings.php`), this must match one of the trusted hosts. + * **SERVER_ADDRESS:** IP address of the server. + * **DOCUMENT_ROOT:** Absolute filepath of the web root directory. + +Example: + +```bash +SCRIPT_NAME=/index.php \ +SERVER_NAME=localhost \ +SERVER_ADDRESS=127.0.0.1 \ +DOCUMENT_ROOT=/var/www/html \ +/var/www/html/vendor/bin/ppm start /var/www/html \ +--bridge=PHPPM\\Bridges\\DrupalKernel \ +--bootstrap=PHPPM\\Bootstraps\\Drupal +``` diff --git a/composer.json b/composer.json index 3f54c01..98397a1 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "kentr/php-pm-drupal-adapter", + "name": "php-pm/drupal-adapter", "require": { "php-pm/php-pm": "*", "php-pm/httpkernel-adapter": "*" diff --git a/patches/kentr-allow-repeated-setSitePath-in-DrupalKernel.patch b/patches/kentr-allow-repeated-setSitePath-in-DrupalKernel.patch index 43bd297..06f4f0f 100644 --- a/patches/kentr-allow-repeated-setSitePath-in-DrupalKernel.patch +++ b/patches/kentr-allow-repeated-setSitePath-in-DrupalKernel.patch @@ -1,8 +1,8 @@ diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php -index 16b3bac..c84286b 100644 +index 274a3fa..9089a7d 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php -@@ -383,7 +383,7 @@ public static function findSitePath(Request $request, $require_settings = TRUE) +@@ -410,7 +410,7 @@ public static function findSitePath(Request $request, $require_settings = TRUE, * {@inheritdoc} */ public function setSitePath($path) {