diff --git a/src/Message/ServerRequest.php b/src/Message/ServerRequest.php index 601499f..6ec61aa 100644 --- a/src/Message/ServerRequest.php +++ b/src/Message/ServerRequest.php @@ -4,7 +4,9 @@ namespace WellRESTed\Message; use InvalidArgumentException; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; +use Psr\Http\Message\UriInterface; /** * Representation of an incoming, server-side HTTP request. @@ -51,9 +53,31 @@ class ServerRequest extends Request implements ServerRequestInterface // ------------------------------------------------------------------------- - public function __construct(array $serverParams = []) - { - parent::__construct(); + /** + * Create a new ServerRequest. + * + * $headers is an optional associative array with header field names as + * string keys and values as either string or string[]. + * + * If no StreamInterface is provided for $body, the instance will create + * a NullStream instance for the message body. + * + * @param string $method + * @param string|UriInterface $uri + * @param array $headers Associative array with header field names as + * keys and values as string|string[] + * @param StreamInterface|null $body A stream representation of the message + * entity body + * @param array $serverParams An array of Server API (SAPI) parameters + */ + public function __construct( + string $method = 'GET', + $uri = '', + array $headers = [], + ?StreamInterface $body = null, + array $serverParams = [] + ){ + parent::__construct($method, $uri, $headers, $body); $this->serverParams = $serverParams; $this->cookieParams = []; $this->queryParams = []; diff --git a/src/Message/ServerRequestMarshaller.php b/src/Message/ServerRequestMarshaller.php index 9b67065..ae61317 100644 --- a/src/Message/ServerRequestMarshaller.php +++ b/src/Message/ServerRequestMarshaller.php @@ -8,38 +8,28 @@ use Psr\Http\Message\UriInterface; class ServerRequestMarshaller { - public function getServerRequest( - ?array $serverParams = null, - ?array $cookieParams = null, - ?array $queryParams = null, - ?array $postParams = null, - ?array $fileParams = null, - string $inputStream = 'php://input' - ): ServerRequestInterface { - $serverParams = $serverParams ?? $_SERVER; - $cookieParams = $cookieParams ?? $_COOKIE; - $queryParams = $queryParams ?? self::parseQuery($serverParams); - $postParams = $postParams ?? $_POST; - $fileParams = $fileParams ?? $_FILES; + /** + * Read the request as sent from the client and construct a ServerRequest + * representation. + * + * @return ServerRequestInterface + * @internal + */ + public function getServerRequest(): ServerRequestInterface + { + $method = self::parseMethod($_SERVER); + $uri = self::readUri($_SERVER); + $headers = self::parseHeaders($_SERVER); + $body = self::readBody(); - $request = new ServerRequest($serverParams); - - $request = $request - ->withProtocolVersion(self::parseProtocolVersion($serverParams)) - ->withMethod(self::parseMethod($serverParams)) - ->withBody(self::readBody($inputStream)) - ->withUri(self::readUri($serverParams)) - ->withUploadedFiles(self::readUploadedFiles($fileParams)) - ->withCookieParams($cookieParams) - ->withQueryParams($queryParams); - - $headers = self::parseHeaders($serverParams); - foreach ($headers as $name => $value) { - $request = $request->withAddedHeader($name, $value); - } + $request = (new ServerRequest($method, $uri, $headers, $body, $_SERVER)) + ->withProtocolVersion(self::parseProtocolVersion($_SERVER)) + ->withUploadedFiles(self::readUploadedFiles($_FILES)) + ->withCookieParams($_COOKIE) + ->withQueryParams(self::parseQuery($_SERVER)); if (self::isForm($request)) { - $request = $request->withParsedBody($postParams); + $request = $request->withParsedBody($_POST); } return $request; @@ -95,9 +85,9 @@ class ServerRequestMarshaller return $serverParams['REQUEST_METHOD'] ?? 'GET'; } - private static function readBody(string $inputStream): StreamInterface + private static function readBody(): StreamInterface { - $input = fopen($inputStream, 'rb'); + $input = fopen('php://input', 'rb'); $temp = fopen('php://temp', 'wb+'); stream_copy_to_stream($input, $temp); rewind($temp); diff --git a/tests/Message/ServerRequestMarshallerTest.php b/tests/Message/ServerRequestMarshallerTest.php index c579371..1bf829f 100644 --- a/tests/Message/ServerRequestMarshallerTest.php +++ b/tests/Message/ServerRequestMarshallerTest.php @@ -28,6 +28,8 @@ class ServerRequestMarshallerTest extends TestCase 'hamster' => 'Dusty' ]; + FopenHelper::$inputTempFile = null; + $this->marshaller = new ServerRequestMarshaller(); } @@ -103,15 +105,9 @@ class ServerRequestMarshallerTest extends TestCase $tempFilePath = tempnam(sys_get_temp_dir(), 'test'); $content = 'Body content'; file_put_contents($tempFilePath, $content); + FopenHelper::$inputTempFile = $tempFilePath; - $request = $this->marshaller->getServerRequest( - null, - null, - null, - null, - null, - $tempFilePath - ); + $request = $this->marshaller->getServerRequest(); unlink($tempFilePath); $this->assertEquals($content, (string) $request->getBody()); @@ -182,7 +178,8 @@ class ServerRequestMarshallerTest extends TestCase */ public function testProvidesUri(UriInterface $expected, array $serverParams): void { - $request = $this->marshaller->getServerRequest($serverParams); + $_SERVER = $serverParams; + $request = $this->marshaller->getServerRequest(); $this->assertEquals($expected, $request->getUri()); } @@ -379,3 +376,25 @@ class ServerRequestMarshallerTest extends TestCase ]; } } + +// ----------------------------------------------------------------------------- + +// Declare fopen function in this namespace so the class under test will use +// this instead of the internal global functions during testing. + +class FopenHelper +{ + /** + * @var string Path to temp file to read in place of 'php://input' + */ + public static $inputTempFile; +} + +function fopen($filename, $mode) +{ + if (FopenHelper::$inputTempFile && $filename === 'php://input') { + $filename = FopenHelper::$inputTempFile; + } + + return \fopen($filename, $mode); +}