Browse Source

Start refactoring URL handling to use PSR7 implementation.

Convert the internals of URL handling in the request class to use
the UriInterface implementation in diactoros. This will let us remove
a bunch of code from `Request` and replace it with code from diactoros
and our own ServerRequestFactory.

There are still a bunch of rough edges. Particularily around how the
base URL and webroot directory are being handled.
Mark Story 9 years ago
parent
commit
dbc5d413cc
3 changed files with 81 additions and 23 deletions
  1. 42 11
      src/Http/ServerRequestFactory.php
  2. 38 11
      src/Network/Request.php
  3. 1 1
      tests/TestCase/Network/RequestTest.php

+ 42 - 11
src/Http/ServerRequestFactory.php

@@ -39,7 +39,7 @@ abstract class ServerRequestFactory extends BaseFactory
         array $files = null
     ) {
         $request = parent::fromGlobals($server, $query, $body, $cookies, $files);
-        list($base, $webroot) = static::getBase($request);
+        list($base, $webroot) = static::getBase($request->getUri(), $request->getServerParams());
 
         $sessionConfig = (array)Configure::read('Session') + [
             'defaults' => 'php',
@@ -51,26 +51,59 @@ abstract class ServerRequestFactory extends BaseFactory
             ->withAttribute('session', $session);
 
         if ($base) {
-            $request = static::updatePath($base, $request);
+            $request = static::updatePath($base, $request->getUri());
         }
 
         return $request;
     }
 
     /**
+     * Create a new Uri instance from the provided server data.
+     *
+     * @param array $server Array of server data to build the Uri from.
+     *   $_SERVER will be added into the $server parameter.
+     * @return \Psr\Http\Message\UriInterface New instance.
+     */
+    public static function createUri(array $server = [])
+    {
+        $server += $_SERVER;
+        $server = static::normalizeServer($server);
+        $headers = static::marshalHeaders($server);
+
+        $uri = static::marshalUriFromServer($server, $headers);
+        list($base, $webroot) = static::getBase($uri, $server);
+
+        // Look in PATH_INFO first, as this is the exact value we need prepared
+        // by PHP.
+        $pathInfo = Hash::get($server, 'PATH_INFO');
+        if ($pathInfo) {
+            $uri = $uri->withPath($pathInfo);
+        } else {
+            $uri = static::updatePath($base, $uri);
+        }
+
+        // TODO Find an alternate solution to this.
+        $uri->base = $base;
+        $uri->webroot = $webroot;
+        return $uri;
+    }
+
+    /**
      * Updates the request URI to remove the base directory.
      *
      * @param string $base The base path to remove.
-     * @param \Psr\Http\Message\ServerRequestInterface $request The request to modify.
-     * @return \Psr\Http\Message\ServerRequestInterface The modified request.
+     * @param \Psr\Http\Message\UriInterface $uri The uri to update.
+     * @return \Psr\Http\Message\ServerRequestInterface The modified Uri instance.
      */
-    protected static function updatePath($base, $request)
+    protected static function updatePath($base, $uri)
     {
-        $uri = $request->getUri();
         $path = $uri->getPath();
         if (strlen($base) > 0 && strpos($path, $base) === 0) {
             $path = substr($path, strlen($base));
         }
+        if ($path === '/index.php' && $uri->getQuery()) {
+            $path = $uri->getQuery();
+        }
         if (empty($path) || $path === '/' || $path === '//' || $path === '/index.php') {
             $path = '/';
         }
@@ -81,8 +114,7 @@ abstract class ServerRequestFactory extends BaseFactory
         ) {
             $path = '/';
         }
-
-        return $request->withUri($uri->withPath($path));
+        return $uri->withPath($path);
     }
 
     /**
@@ -93,10 +125,9 @@ abstract class ServerRequestFactory extends BaseFactory
      * @param \Psr\Http\Message\ServerRequestInterface $request The request.
      * @return array An array containing the [baseDir, webroot]
      */
-    protected static function getBase($request)
+    protected static function getBase($uri, $server)
     {
-        $path = $request->getUri()->getPath();
-        $server = $request->getServerParams();
+        $path = $uri->getPath();
 
         $base = $webroot = $baseUrl = null;
         $config = Configure::read('App');

+ 38 - 11
src/Network/Request.php

@@ -17,11 +17,13 @@ namespace Cake\Network;
 use ArrayAccess;
 use BadMethodCallException;
 use Cake\Core\Configure;
+use Cake\Http\ServerRequestFactory;
 use Cake\Network\Exception\MethodNotAllowedException;
 use Cake\Utility\Hash;
 use InvalidArgumentException;
 use Psr\Http\Message\StreamInterface;
 use Psr\Http\Message\UploadedFileInterface;
+use Psr\Http\Message\UriInterface;
 use Zend\Diactoros\PhpInputStream;
 use Zend\Diactoros\Stream;
 use Zend\Diactoros\UploadedFile;
@@ -203,7 +205,10 @@ class Request implements ArrayAccess
      */
     public static function createFromGlobals()
     {
-        list($base, $webroot) = static::_base();
+        $uri = ServerRequestFactory::createUri($_SERVER);
+        $base = $uri->base;
+        $webroot = $uri->webroot;
+
         $sessionConfig = (array)Configure::read('Session') + [
             'defaults' => 'php',
             'cookiePath' => $webroot
@@ -215,11 +220,11 @@ class Request implements ArrayAccess
             'files' => $_FILES,
             'cookies' => $_COOKIE,
             'environment' => $_SERVER + $_ENV,
+            'uri' => $uri,
             'base' => $base,
             'webroot' => $webroot,
             'session' => Session::create($sessionConfig)
         ];
-        $config['url'] = static::_url($config);
 
         return new static($config);
     }
@@ -237,6 +242,7 @@ class Request implements ArrayAccess
      * - `cookies` Cookies for this request.
      * - `environment` $_SERVER and $_ENV data.
      * - `url` The URL without the base path for the request.
+     * - `uri` The PSR7 UriInterface object. If null, one will be created.
      * - `base` The base URL for the request.
      * - `webroot` The webroot directory for the request.
      * - `input` The data that would come from php://input this is useful for simulating
@@ -258,6 +264,7 @@ class Request implements ArrayAccess
             'cookies' => [],
             'environment' => [],
             'url' => '',
+            'uri' => null,
             'base' => '',
             'webroot' => '',
             'input' => null,
@@ -284,12 +291,32 @@ class Request implements ArrayAccess
             ]);
         }
 
-        $this->url = $config['url'];
-        $this->base = $config['base'];
+        $this->_environment = $config['environment'];
         $this->cookies = $config['cookies'];
-        $this->here = $this->base . '/' . $this->url;
+
+        if (isset($config['uri']) && $config['uri'] instanceof UriInterface) {
+            $uri = $config['uri'];
+        } else {
+            $uri = ServerRequestFactory::createUri($config['environment']);
+        }
+
+        // Extract a query string from config[url] if present.
+        // This is required for backwards compatbility and keeping
+        // UriInterface implementations happy.
+        $querystr = '';
+        if (strpos($config['url'], '?') !== false) {
+            list($config['url'], $querystr) = explode('?', $config['url']);
+        }
+        if ($config['url']) {
+            $uri = $uri->withPath('/' . $config['url']);
+        }
+
+        $this->uri = $uri;
+        $this->base = $config['base'];
         $this->webroot = $config['webroot'];
-        $this->_environment = $config['environment'];
+
+        $this->url = substr($uri->getPath(), 1);
+        $this->here = $this->base . '/' . $this->url;
 
         if (isset($config['input'])) {
             $stream = new Stream('php://memory', 'rw');
@@ -302,7 +329,7 @@ class Request implements ArrayAccess
 
         $config['post'] = $this->_processPost($config['post']);
         $this->data = $this->_processFiles($config['post'], $config['files']);
-        $this->query = $this->_processGet($config['query']);
+        $this->query = $this->_processGet($config['query'], $querystr);
         $this->params = $config['params'];
         $this->_session = $config['session'];
     }
@@ -347,17 +374,17 @@ class Request implements ArrayAccess
     /**
      * Process the GET parameters and move things into the object.
      *
+     * @param string $queryString A query string from the URL if provided
      * @param array $query The array to which the parsed keys/values are being added.
      * @return array An array containing the parsed querystring keys/values.
      */
-    protected function _processGet($query)
+    protected function _processGet($query, $queryString = '')
     {
         $unsetUrl = '/' . str_replace(['.', ' '], '_', urldecode($this->url));
         unset($query[$unsetUrl]);
         unset($query[$this->base . $unsetUrl]);
-        if (strpos($this->url, '?') !== false) {
-            list(, $querystr) = explode('?', $this->url);
-            parse_str($querystr, $queryArgs);
+        if (strlen($queryString)) {
+            parse_str($queryString, $queryArgs);
             $query += $queryArgs;
         }
 

+ 1 - 1
tests/TestCase/Network/RequestTest.php

@@ -169,7 +169,7 @@ class RequestTest extends TestCase
         $request = new Request(['url' => 'some/path?one=something&two=else']);
         $expected = ['one' => 'something', 'two' => 'else'];
         $this->assertEquals($expected, $request->query);
-        $this->assertEquals('some/path?one=something&two=else', $request->url);
+        $this->assertEquals('some/path', $request->url);
     }
 
     /**