Extract server request marshalling to own class.

This commit is contained in:
PJ Dietz 2020-08-11 09:11:30 -04:00
parent cd2e4448e2
commit 168867206e
7 changed files with 618 additions and 630 deletions

View File

@ -30,6 +30,7 @@
},
"autoload-dev": {
"psr-4": {
"WellRESTed\\": "test/tests/unit/",
"WellRESTed\\Test\\": "test/src/"
}
}

View File

@ -4,9 +4,7 @@ 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.
@ -53,22 +51,13 @@ class ServerRequest extends Request implements ServerRequestInterface
// ------------------------------------------------------------------------
/**
* Creates a new, empty representation of a server-side HTTP request.
*
* To obtain a ServerRequest representing the request sent to the server
* instantiating the request, use the factory method
* ServerRequest::getServerRequest
*
* @see ServerRequest::getServerRequest
*/
public function __construct()
public function __construct(array $serverParams = [])
{
parent::__construct();
$this->attributes = [];
$this->serverParams = $serverParams;
$this->cookieParams = [];
$this->queryParams = [];
$this->serverParams = [];
$this->attributes = [];
$this->uploadedFiles = [];
}
@ -339,189 +328,6 @@ class ServerRequest extends Request implements ServerRequestInterface
// ------------------------------------------------------------------------
/**
* @param array $attributes
* @return void
*/
protected function readFromServerRequest(array $attributes = [])
{
$this->attributes = $attributes;
$this->serverParams = $_SERVER;
$this->cookieParams = $_COOKIE;
$this->readUploadedFiles($_FILES);
$this->queryParams = [];
$this->uri = $this->readUri();
if (isset($_SERVER['QUERY_STRING'])) {
parse_str($_SERVER['QUERY_STRING'], $this->queryParams);
}
if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.0') {
// The default is 1.1, so only update if 1.0
$this->protocolVersion = '1.0';
}
if (isset($_SERVER['REQUEST_METHOD'])) {
$this->method = $_SERVER['REQUEST_METHOD'];
}
$headers = $this->getServerRequestHeaders();
foreach ($headers as $key => $value) {
$this->headers[$key] = $value;
}
$this->body = $this->getStreamForBody();
$contentType = $this->getHeaderLine('Content-type');
if (strpos($contentType, 'application/x-www-form-urlencoded') !== false
|| strpos($contentType, 'multipart/form-data') !== false) {
$this->parsedBody = $_POST;
}
}
protected function readUploadedFiles(array $input): void
{
$uploadedFiles = [];
foreach ($input as $name => $value) {
$this->addUploadedFilesToBranch($uploadedFiles, $name, $value);
}
$this->uploadedFiles = $uploadedFiles;
}
protected function addUploadedFilesToBranch(
array &$branch,
string $name,
array $value
): void {
// Check for each of the expected keys.
if (isset($value['name'], $value['type'], $value['tmp_name'], $value['error'], $value['size'])) {
// This is a file. It may be a single file, or a list of files.
// Check if these items are arrays.
if (is_array($value['name'])
&& is_array($value['type'])
&& is_array($value['tmp_name'])
&& is_array($value['error'])
&& is_array($value['size'])
) {
// Each item is an array. This is a list of uploaded files.
$files = [];
$keys = array_keys($value['name']);
foreach ($keys as $key) {
$files[$key] = new UploadedFile(
$value['name'][$key],
$value['type'][$key],
$value['size'][$key],
$value['tmp_name'][$key],
$value['error'][$key]
);
}
$branch[$name] = $files;
} else {
// All expected keys are present and are not arrays. This is an uploaded file.
$uploadedFile = new UploadedFile(
$value['name'],
$value['type'],
$value['size'],
$value['tmp_name'],
$value['error']
);
$branch[$name] = $uploadedFile;
}
} else {
// Add another branch
$nextBranch = [];
foreach ($value as $nextName => $nextValue) {
$this->addUploadedFilesToBranch($nextBranch, $nextName, $nextValue);
}
$branch[$name] = $nextBranch;
}
}
protected function readUri(): UriInterface
{
$uri = '';
$scheme = 'http';
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] && $_SERVER['HTTPS'] !== 'off') {
$scheme = 'https';
}
if (isset($_SERVER['HTTP_HOST'])) {
$authority = $_SERVER['HTTP_HOST'];
$uri .= "$scheme://$authority";
}
// Path and query string
if (isset($_SERVER['REQUEST_URI'])) {
$uri .= $_SERVER['REQUEST_URI'];
}
return new Uri($uri);
}
/**
* Return a reference to the singleton instance of the Request derived
* from the server's information about the request sent to the server.
*
* @param array $attributes Key-value pairs to add to the request.
* @return static
* @static
*/
public static function getServerRequest(array $attributes = [])
{
$request = new static();
$request->readFromServerRequest($attributes);
return $request;
}
/**
* Return a stream representing the request's body.
*
* Override this method to use a specific StreamInterface implementation.
*
* @return StreamInterface
*/
protected function getStreamForBody()
{
$input = fopen('php://input', 'rb');
$temp = fopen('php://temp', 'wb+');
stream_copy_to_stream($input, $temp);
rewind($temp);
return new Stream($temp);
}
/**
* Read and return all request headers from the request issued to the server.
*
* @return array Associative array of headers
*/
protected function getServerRequestHeaders()
{
// http://www.php.net/manual/en/function.getallheaders.php#84262
$headers = [];
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) === 'HTTP_') {
$name = $this->normalizeHeaderName(substr($name, 5));
$headers[$name] = trim($value);
} elseif ($this->isContentHeader($name) && !empty(trim($value))) {
$name = $this->normalizeHeaderName($name);
$headers[$name] = trim($value);
}
}
return $headers;
}
private function isContentHeader(string $name): bool
{
return $name === 'CONTENT_LENGTH' || $name === 'CONTENT_TYPE';
}
/**
* @param string $name
* @return string
*/
private function normalizeHeaderName($name)
{
$name = ucwords(strtolower(str_replace('_', ' ', $name)));
return str_replace(' ', '-', $name);
}
/**
* @param array $root
* @return bool

View File

@ -0,0 +1,201 @@
<?php
namespace WellRESTed\Message;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
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;
$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);
}
if (self::isForm($request)) {
$request = $request->withParsedBody($postParams);
}
return $request;
}
private static function parseQuery(array $serverParams): array
{
$queryParams = [];
if (isset($serverParams['QUERY_STRING'])) {
parse_str($serverParams['QUERY_STRING'], $queryParams);
}
return $queryParams;
}
private static function parseProtocolVersion(array $serverParams): string
{
if (isset($serverParams['SERVER_PROTOCOL'])
&& $serverParams['SERVER_PROTOCOL'] === 'HTTP/1.0') {
return '1.0';
}
return '1.1';
}
private static function parseHeaders(array $serverParams): array
{
// http://www.php.net/manual/en/function.getallheaders.php#84262
$headers = [];
foreach ($serverParams as $name => $value) {
if (substr($name, 0, 5) === 'HTTP_') {
$name = self::normalizeHeaderName(substr($name, 5));
$headers[$name] = trim($value);
} elseif (self::isContentHeader($name) && !empty(trim($value))) {
$name = self::normalizeHeaderName($name);
$headers[$name] = trim($value);
}
}
return $headers;
}
private static function normalizeHeaderName(string $name): string
{
$name = ucwords(strtolower(str_replace('_', ' ', $name)));
return str_replace(' ', '-', $name);
}
private static function isContentHeader(string $name): bool
{
return $name === 'CONTENT_LENGTH' || $name === 'CONTENT_TYPE';
}
private static function parseMethod(array $serverParams): string
{
return $serverParams['REQUEST_METHOD'] ?? 'GET';
}
private static function readBody(string $inputStream): StreamInterface
{
$input = fopen($inputStream, 'rb');
$temp = fopen('php://temp', 'wb+');
stream_copy_to_stream($input, $temp);
rewind($temp);
return new Stream($temp);
}
private static function readUri(array $serverParams): UriInterface
{
$uri = '';
$scheme = 'http';
if (isset($serverParams['HTTPS']) && $serverParams['HTTPS'] && $serverParams['HTTPS'] !== 'off') {
$scheme = 'https';
}
if (isset($serverParams['HTTP_HOST'])) {
$authority = $serverParams['HTTP_HOST'];
$uri .= "$scheme://$authority";
}
// Path and query string
if (isset($serverParams['REQUEST_URI'])) {
$uri .= $serverParams['REQUEST_URI'];
}
return new Uri($uri);
}
private static function isForm(ServerRequestInterface $request): bool
{
$contentType = $request->getHeaderLine('Content-type');
return (strpos($contentType, 'application/x-www-form-urlencoded') !== false)
|| (strpos($contentType, 'multipart/form-data') !== false);
}
private static function readUploadedFiles(array $input): array
{
$uploadedFiles = [];
foreach ($input as $name => $value) {
self::addUploadedFilesToBranch($uploadedFiles, $name, $value);
}
return $uploadedFiles;
}
private static function addUploadedFilesToBranch(
array &$branch,
string $name,
array $value
): void {
if (self::isUploadedFile($value)) {
if (self::isUploadedFileList($value)) {
$files = [];
$keys = array_keys($value['name']);
foreach ($keys as $key) {
$files[$key] = new UploadedFile(
$value['name'][$key],
$value['type'][$key],
$value['size'][$key],
$value['tmp_name'][$key],
$value['error'][$key]
);
}
$branch[$name] = $files;
} else {
// Single uploaded file
$uploadedFile = new UploadedFile(
$value['name'],
$value['type'],
$value['size'],
$value['tmp_name'],
$value['error']
);
$branch[$name] = $uploadedFile;
}
} else {
// Add another branch
$nextBranch = [];
foreach ($value as $nextName => $nextValue) {
self::addUploadedFilesToBranch($nextBranch, $nextName, $nextValue);
}
$branch[$name] = $nextBranch;
}
}
private static function isUploadedFile(array $value): bool
{
// Check for each of the expected keys. If all are present, this is a
// a file. It may be a single file, or a list of files.
return isset($value['name'], $value['type'], $value['tmp_name'], $value['error'], $value['size']);
}
private static function isUploadedFileList(array $value): bool
{
// When each item is an array, this is a list of uploaded files.
return is_array($value['name'])
&& is_array($value['type'])
&& is_array($value['tmp_name'])
&& is_array($value['error'])
&& is_array($value['size']);
}
}

View File

@ -7,7 +7,7 @@ use Psr\Http\Message\ServerRequestInterface;
use WellRESTed\Dispatching\Dispatcher;
use WellRESTed\Dispatching\DispatcherInterface;
use WellRESTed\Message\Response;
use WellRESTed\Message\ServerRequest;
use WellRESTed\Message\ServerRequestMarshaller;
use WellRESTed\Routing\Router;
use WellRESTed\Transmission\Transmitter;
use WellRESTed\Transmission\TransmitterInterface;
@ -163,7 +163,8 @@ class Server
private function getRequest(): ServerRequestInterface
{
if (!$this->request) {
$this->request = ServerRequest::getServerRequest();
$marshaller = new ServerRequestMarshaller();
return $marshaller->getServerRequest();
}
return $this->request;
}

View File

@ -0,0 +1,381 @@
<?php
namespace WellRESTed\Message;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;
use WellRESTed\Test\TestCase;
/** @backupGlobals enabled */
class ServerRequestMarshallerTest extends TestCase
{
/** @var ServerRequestMarshaller */
private $marshaller;
protected function setUp(): void
{
parent::setUp();
$_SERVER = [
'HTTP_HOST' => 'localhost',
'HTTP_ACCEPT' => 'application/json',
'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded',
'QUERY_STRING' => 'cat=molly&kitten=aggie'
];
$_COOKIE = [
'dog' => 'Bear',
'hamster' => 'Dusty'
];
$this->marshaller = new ServerRequestMarshaller();
}
// -------------------------------------------------------------------------
// Psr\Http\Message\MessageInterface
// -------------------------------------------------------------------------
// Protocol Version
/**
* @dataProvider protocolVersionProvider
* @param $expectedProtocol
* @param $actualProtocol
*/
public function testProvidesProtocolVersion(string $expectedProtocol, ?string $actualProtocol): void
{
$_SERVER['SERVER_PROTOCOL'] = $actualProtocol;
$request = $this->marshaller->getServerRequest();
$this->assertEquals($expectedProtocol, $request->getProtocolVersion());
}
public function protocolVersionProvider(): array
{
return [
['1.1', 'HTTP/1.1'],
['1.0', 'HTTP/1.0'],
['1.1', null],
['1.1', 'INVALID']
];
}
// -------------------------------------------------------------------------
// Headers
public function testProvidesHeadersFromHttpFields(): void
{
$_SERVER = [
'HTTP_ACCEPT' => 'application/json',
'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded'
];
$request = $this->marshaller->getServerRequest();
$this->assertEquals(['application/json'], $request->getHeader('Accept'));
$this->assertEquals(['application/x-www-form-urlencoded'], $request->getHeader('Content-type'));
}
public function testProvidesApacheContentHeaders(): void
{
$_SERVER = [
'CONTENT_LENGTH' => '1024',
'CONTENT_TYPE' => 'application/json'
];
$request = $this->marshaller->getServerRequest();
$this->assertEquals('1024', $request->getHeaderLine('Content-length'));
$this->assertEquals('application/json', $request->getHeaderLine('Content-type'));
}
public function testDoesNotProvideEmptyApacheContentHeaders(): void
{
$_SERVER = [
'CONTENT_LENGTH' => '',
'CONTENT_TYPE' => ' '
];
$request = $this->marshaller->getServerRequest();
$this->assertFalse($request->hasHeader('Content-length'));
$this->assertFalse($request->hasHeader('Content-type'));
}
// -------------------------------------------------------------------------
// Body
public function testProvidesBodyFromInputStream(): void
{
$tempFilePath = tempnam(sys_get_temp_dir(), 'test');
$content = 'Body content';
file_put_contents($tempFilePath, $content);
$request = $this->marshaller->getServerRequest(
null,
null,
null,
null,
null,
$tempFilePath
);
unlink($tempFilePath);
$this->assertEquals($content, (string) $request->getBody());
}
// -------------------------------------------------------------------------
// Psr\Http\Message\RequestInterface
// ------------------------------------------------------------------------
// Request Target
/**
* @dataProvider requestTargetProvider
* @param $expectedRequestTarget
* @param $actualRequestUri
*/
public function testProvidesRequestTarget(string $expectedRequestTarget, ?string $actualRequestUri): void
{
$_SERVER['REQUEST_URI'] = $actualRequestUri;
$request = $this->marshaller->getServerRequest();
$this->assertEquals($expectedRequestTarget, $request->getRequestTarget());
}
public function requestTargetProvider(): array
{
return [
['/', '/'],
['/hello', '/hello'],
['/my/path.txt', '/my/path.txt'],
['/', null]
];
}
// ------------------------------------------------------------------------
// Method
/**
* @dataProvider methodProvider
* @param $expectedMethod
* @param $serverMethod
*/
public function testProvidesMethod($expectedMethod, $serverMethod)
{
$_SERVER['REQUEST_METHOD'] = $serverMethod;
$request = $this->marshaller->getServerRequest();
$this->assertEquals($expectedMethod, $request->getMethod());
}
public function methodProvider()
{
return [
['GET', 'GET'],
['POST', 'POST'],
['DELETE', 'DELETE'],
['PUT', 'PUT'],
['OPTIONS', 'OPTIONS'],
['GET', null]
];
}
// ------------------------------------------------------------------------
// URI
/**
* @dataProvider uriProvider
* @param UriInterface $expected
* @param array $serverParams
*/
public function testProvidesUri(UriInterface $expected, array $serverParams): void
{
$request = $this->marshaller->getServerRequest($serverParams);
$this->assertEquals($expected, $request->getUri());
}
public function uriProvider()
{
return [
[
new Uri('http://localhost/path'),
[
'HTTPS' => 'off',
'HTTP_HOST' => 'localhost',
'REQUEST_URI' => '/path',
'QUERY_STRING' => ''
]
],
[
new Uri('https://foo.com/path/to/stuff?cat=molly'),
[
'HTTPS' => '1',
'HTTP_HOST' => 'foo.com',
'REQUEST_URI' => '/path/to/stuff?cat=molly',
'QUERY_STRING' => 'cat=molly'
]
],
[
new Uri('http://foo.com:8080/path/to/stuff?cat=molly'),
[
'HTTP' => '1',
'HTTP_HOST' => 'foo.com:8080',
'REQUEST_URI' => '/path/to/stuff?cat=molly',
'QUERY_STRING' => 'cat=molly'
]
]
];
}
// -------------------------------------------------------------------------
// Psr\Http\Message\ServerRequestInterface
// -------------------------------------------------------------------------
// Server Params
public function testProvidesServerParams(): void
{
$request = $this->marshaller->getServerRequest();
$this->assertEquals($_SERVER, $request->getServerParams());
}
// ------------------------------------------------------------------------
// Cookies
public function testProvidesCookieParams(): void
{
$request = $this->marshaller->getServerRequest();
$this->assertEquals($_COOKIE, $request->getCookieParams());
}
// ------------------------------------------------------------------------
// Query
public function testProvidesQueryParams(): void
{
$request = $this->marshaller->getServerRequest();
$query = $request->getQueryParams();
$this->assertCount(2, $query);
$this->assertEquals('molly', $query['cat']);
$this->assertEquals('aggie', $query['kitten']);
}
// ------------------------------------------------------------------------
// Uploaded Files
/**
* @dataProvider uploadedFileProvider
* @param UploadedFileInterface $file
* @param array $path
*/
public function testGetServerRequestReadsUploadedFiles(UploadedFileInterface $file, array $path): void
{
$_FILES = [
'single' => [
'name' => 'single.txt',
'type' => 'text/plain',
'tmp_name' => '/tmp/php9hNlHe',
'error' => UPLOAD_ERR_OK,
'size' => 524
],
'nested' => [
'level2' => [
'name' => 'nested.json',
'type' => 'application/json',
'tmp_name' => '/tmp/phpadhjk',
'error' => UPLOAD_ERR_OK,
'size' => 1024
]
],
'nestedList' => [
'level2' => [
'name' => [
0 => 'nestedList0.jpg',
1 => 'nestedList1.jpg',
2 => ''
],
'type' => [
0 => 'image/jpeg',
1 => 'image/jpeg',
2 => ''
],
'tmp_name' => [
0 => '/tmp/phpjpg0',
1 => '/tmp/phpjpg1',
2 => ''
],
'error' => [
0 => UPLOAD_ERR_OK,
1 => UPLOAD_ERR_OK,
2 => UPLOAD_ERR_NO_FILE
],
'size' => [
0 => 256,
1 => 4096,
2 => 0
]
]
],
'nestedDictionary' => [
'level2' => [
'name' => [
'file0' => 'nestedDictionary0.jpg',
'file1' => 'nestedDictionary1.jpg'
],
'type' => [
'file0' => 'image/png',
'file1' => 'image/png'
],
'tmp_name' => [
'file0' => '/tmp/phppng0',
'file1' => '/tmp/phppng1'
],
'error' => [
'file0' => UPLOAD_ERR_OK,
'file1' => UPLOAD_ERR_OK
],
'size' => [
'file0' => 256,
'file1' => 4096
]
]
]
];
$request = $this->marshaller->getServerRequest();
$current = $request->getUploadedFiles();
foreach ($path as $item) {
$current = $current[$item];
}
$this->assertEquals($file, $current);
}
public function uploadedFileProvider(): array
{
return [
[new UploadedFile('single.txt', 'text/plain', 524, '/tmp/php9hNlHe', UPLOAD_ERR_OK), ['single']],
[new UploadedFile('nested.json', 'application/json', 1024, '/tmp/phpadhjk', UPLOAD_ERR_OK), ['nested', 'level2']],
[new UploadedFile('nestedList0.jpg', 'image/jpeg', 256, '/tmp/phpjpg0', UPLOAD_ERR_OK), ['nestedList', 'level2', 0]],
[new UploadedFile('nestedList1.jpg', 'image/jpeg', 4096, '/tmp/phpjpg1', UPLOAD_ERR_OK), ['nestedList', 'level2', 1]],
[new UploadedFile('', '', 0, '', UPLOAD_ERR_NO_FILE), ['nestedList', 'level2', 2]],
[new UploadedFile('nestedDictionary0.jpg', 'image/png', 256, '/tmp/phppng0', UPLOAD_ERR_OK), ['nestedDictionary', 'level2', 'file0']],
[new UploadedFile('nestedDictionary1.jpg', 'image/png', 4096, '/tmp/phppngg1', UPLOAD_ERR_OK), ['nestedDictionary', 'level2', 'file1']]
];
}
// ------------------------------------------------------------------------
// Parsed Body
/**
* @dataProvider formContentTypeProvider
* @param string $contentType
*/
public function testProvidesParsedBodyForForms(string $contentType): void
{
$_SERVER['HTTP_CONTENT_TYPE'] = $contentType;
$_POST = [
'dog' => 'Bear'
];
$request = $this->marshaller->getServerRequest();
$this->assertEquals('Bear', $request->getParsedBody()['dog']);
}
public function formContentTypeProvider(): array
{
return [
['application/x-www-form-urlencoded'],
['multipart/form-data']
];
}
}

View File

@ -1,380 +1,12 @@
<?php
namespace WellRESTed\Test\Unit\Message;
namespace WellRESTed\Message;
use InvalidArgumentException;
use WellRESTed\Message\NullStream;
use WellRESTed\Message\ServerRequest;
use WellRESTed\Message\UploadedFile;
use WellRESTed\Message\Uri;
use WellRESTed\Test\TestCase;
class ServerRequestTest extends TestCase
{
// ------------------------------------------------------------------------
// Construction and Marshalling
/** @backupGlobals enabled */
public function testGetServerRequestReadsFromRequest()
{
$_SERVER = [
'HTTP_HOST' => 'localhost',
'HTTP_ACCEPT' => 'application/json',
'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded',
'QUERY_STRING' => 'guinea_pig=Claude&hamster=Fizzgig'
];
$_COOKIE = [
'cat' => 'Molly'
];
$_FILES = [];
$_POST = [
'dog' => 'Bear'
];
$attributes = ['guinea_pig' => 'Claude'];
$request = ServerRequest::getServerRequest($attributes);
$this->assertNotNull($request);
return $request;
}
// ------------------------------------------------------------------------
// Marshalling Request Information
/**
* @backupGlobals enabled
* @dataProvider protocolVersionProvider
*/
public function testGetServerRequestReadsProtocolVersion($expectedProtocol, $serverProtocol)
{
$_SERVER = [
'HTTP_HOST' => 'localhost',
'SERVER_PROTOCOL' => $serverProtocol,
'REQUEST_METHOD' => 'GET'
];
$request = ServerRequest::getServerRequest();
$this->assertEquals($expectedProtocol, $request->getProtocolVersion());
}
public function protocolVersionProvider()
{
return [
['1.1', 'HTTP/1.1'],
['1.0', 'HTTP/1.0'],
['1.1', null],
['1.1', 'INVALID']
];
}
/**
* @backupGlobals enabled
* @dataProvider methodProvider
*/
public function testGetServerRequestReadsMethod($expectedMethod, $serverMethod)
{
$_SERVER = [
'HTTP_HOST' => 'localhost',
'REQUEST_METHOD' => $serverMethod
];
$request = ServerRequest::getServerRequest();
$this->assertEquals($expectedMethod, $request->getMethod());
}
public function methodProvider()
{
return [
['GET', 'GET'],
['POST', 'POST'],
['DELETE', 'DELETE'],
['PUT', 'PUT'],
['OPTIONS', 'OPTIONS'],
['GET', null]
];
}
/**
* @backupGlobals enabled
* @dataProvider requestTargetProvider
*/
public function testGetServerRequestReadsRequestTargetFromRequest($expectedRequestTarget, $serverRequestUri)
{
$_SERVER = [
'HTTP_HOST' => 'localhost',
'REQUEST_URI' => $serverRequestUri
];
$request = ServerRequest::getServerRequest();
$this->assertEquals($expectedRequestTarget, $request->getRequestTarget());
}
public function requestTargetProvider()
{
return [
['/', '/'],
['/hello', '/hello'],
['/my/path.txt', '/my/path.txt'],
['/', null]
];
}
/** @depends testGetServerRequestReadsFromRequest */
public function testGetServerRequestReadsHeaders($request)
{
/** @var ServerRequest $request */
$this->assertEquals(['application/json'], $request->getHeader('Accept'));
}
/**
* @backupGlobals enabled
*/
public function testGetServerRequestReadsContentHeaders()
{
$_SERVER = [
'CONTENT_LENGTH' => '1024',
'CONTENT_TYPE' => 'application/json'
];
$request = ServerRequest::getServerRequest();
$this->assertEquals('1024', $request->getHeaderLine('Content-length'));
$this->assertEquals('application/json', $request->getHeaderLine('Content-type'));
}
/**
* @backupGlobals enabled
*/
public function testGetServerRequestDoesNotReadEmptyContentHeaders()
{
$_SERVER = [
'CONTENT_LENGTH' => '',
'CONTENT_TYPE' => ' '
];
$request = ServerRequest::getServerRequest();
$this->assertFalse($request->hasHeader('Content-length'));
$this->assertFalse($request->hasHeader('Content-type'));
}
public function testGetServerRequestReadsBody()
{
$body = new NullStream();
$request = $this->getMockBuilder('WellRESTed\Message\ServerRequest')
->setMethods(['getStreamForBody'])
->getMock();
$request->expects($this->any())
->method('getStreamForBody')
->will($this->returnValue($body));
$called = false;
$callReadFromServerRequest = function () use (&$called) {
$called = true;
$this->readFromServerRequest();
};
$callReadFromServerRequest = $callReadFromServerRequest->bindTo($request, $request);
$callReadFromServerRequest();
$this->assertSame($body, $request->getBody());
}
/**
* @backupGlobals enabled
* @dataProvider uriProvider
*/
public function testGetServerRequestReadsUri($expected, $server)
{
$_SERVER = $server;
$request = ServerRequest::getServerRequest();
$this->assertEquals($expected, $request->getUri());
}
public function uriProvider()
{
return [
[
new Uri('http://localhost/path'),
[
'HTTPS' => 'off',
'HTTP_HOST' => 'localhost',
'REQUEST_URI' => '/path',
'QUERY_STRING' => ''
]
],
[
new Uri('https://foo.com/path/to/stuff?cat=molly'),
[
'HTTPS' => '1',
'HTTP_HOST' => 'foo.com',
'REQUEST_URI' => '/path/to/stuff?cat=molly',
'QUERY_STRING' => 'cat=molly'
]
],
[
new Uri('http://foo.com:8080/path/to/stuff?cat=molly'),
[
'HTTP' => '1',
'HTTP_HOST' => 'foo.com:8080',
'REQUEST_URI' => '/path/to/stuff?cat=molly',
'QUERY_STRING' => 'cat=molly'
]
]
];
}
// ------------------------------------------------------------------------
// Marshalling ServerRequest Information
/** @depends testGetServerRequestReadsFromRequest */
public function testGetServerRequestReadsServerParams($request)
{
/** @var ServerRequest $request */
$this->assertEquals('localhost', $request->getServerParams()['HTTP_HOST']);
}
/** @depends testGetServerRequestReadsFromRequest */
public function testGetServerRequestReadsCookieParams($request)
{
/** @var ServerRequest $request */
$this->assertEquals('Molly', $request->getCookieParams()['cat']);
}
/** @depends testGetServerRequestReadsFromRequest */
public function testGetServerRequestReadsQueryParams($request)
{
/** @var ServerRequest $request */
$this->assertEquals('Claude', $request->getQueryParams()['guinea_pig']);
}
/**
* @backupGlobals enabled
* @dataProvider uploadedFileProvider
*/
public function testGetServerRequestReadsUploadedFiles($file, $path)
{
$_SERVER = [
'HTTP_HOST' => 'localhost',
'HTTP_ACCEPT' => 'application/json',
'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded'
];
$_FILES = [
'single' => [
'name' => 'single.txt',
'type' => 'text/plain',
'tmp_name' => '/tmp/php9hNlHe',
'error' => UPLOAD_ERR_OK,
'size' => 524
],
'nested' => [
'level2' => [
'name' => 'nested.json',
'type' => 'application/json',
'tmp_name' => '/tmp/phpadhjk',
'error' => UPLOAD_ERR_OK,
'size' => 1024
]
],
'nestedList' => [
'level2' => [
'name' => [
0 => 'nestedList0.jpg',
1 => 'nestedList1.jpg',
2 => ''
],
'type' => [
0 => 'image/jpeg',
1 => 'image/jpeg',
2 => ''
],
'tmp_name' => [
0 => '/tmp/phpjpg0',
1 => '/tmp/phpjpg1',
2 => ''
],
'error' => [
0 => UPLOAD_ERR_OK,
1 => UPLOAD_ERR_OK,
2 => UPLOAD_ERR_NO_FILE
],
'size' => [
0 => 256,
1 => 4096,
2 => 0
]
]
],
'nestedDictionary' => [
'level2' => [
'name' => [
'file0' => 'nestedDictionary0.jpg',
'file1' => 'nestedDictionary1.jpg'
],
'type' => [
'file0' => 'image/png',
'file1' => 'image/png'
],
'tmp_name' => [
'file0' => '/tmp/phppng0',
'file1' => '/tmp/phppng1'
],
'error' => [
'file0' => UPLOAD_ERR_OK,
'file1' => UPLOAD_ERR_OK
],
'size' => [
'file0' => 256,
'file1' => 4096
]
]
]
];
$request = ServerRequest::getServerRequest();
$current = $request->getUploadedFiles();
foreach ($path as $item) {
$current = $current[$item];
}
$this->assertEquals($file, $current);
}
public function uploadedFileProvider()
{
return [
[new UploadedFile('single.txt', 'text/plain', 524, '/tmp/php9hNlHe', UPLOAD_ERR_OK), ['single']],
[new UploadedFile('nested.json', 'application/json', 1024, '/tmp/phpadhjk', UPLOAD_ERR_OK), ['nested', 'level2']],
[new UploadedFile('nestedList0.jpg', 'image/jpeg', 256, '/tmp/phpjpg0', UPLOAD_ERR_OK), ['nestedList', 'level2', 0]],
[new UploadedFile('nestedList1.jpg', 'image/jpeg', 4096, '/tmp/phpjpg1', UPLOAD_ERR_OK), ['nestedList', 'level2', 1]],
[new UploadedFile('', '', 0, '', UPLOAD_ERR_NO_FILE), ['nestedList', 'level2', 2]],
[new UploadedFile('nestedDictionary0.jpg', 'image/png', 256, '/tmp/phppng0', UPLOAD_ERR_OK), ['nestedDictionary', 'level2', 'file0']],
[new UploadedFile('nestedDictionary1.jpg', 'image/png', 4096, '/tmp/phppngg1', UPLOAD_ERR_OK), ['nestedDictionary', 'level2', 'file1']]
];
}
/**
* @backupGlobals enabled
* @dataProvider formContentTypeProvider
*/
public function testGetServerRequestParsesFormBody($contentType)
{
$_SERVER = [
'HTTP_HOST' => 'localhost',
'HTTP_CONTENT_TYPE' => $contentType,
];
$_COOKIE = [];
$_FILES = [];
$_POST = [
'dog' => 'Bear'
];
$request = ServerRequest::getServerRequest();
$this->assertEquals('Bear', $request->getParsedBody()['dog']);
}
public function formContentTypeProvider()
{
return [
['application/x-www-form-urlencoded'],
['multipart/form-data']
];
}
/** @depends testGetServerRequestReadsFromRequest */
public function testGetServerRequestProvidesAttributesIfPassed($request)
{
/** @var ServerRequest $request */
$this->assertEquals('Claude', $request->getAttribute('guinea_pig'));
}
// ------------------------------------------------------------------------
// Server Params
@ -393,14 +25,17 @@ class ServerRequestTest extends TestCase
$this->assertEquals([], $request->getCookieParams());
}
/** @depends testGetServerRequestReadsFromRequest */
public function testWithCookieParamsCreatesNewInstance($request1)
public function testWithCookieParamsCreatesNewInstanceWithCookies()
{
/** @var ServerRequest $request1 */
$request2 = $request1->withCookieParams([
$cookies = [
'cat' => 'Oscar'
]);
$this->assertNotEquals($request1->getCookieParams()['cat'], $request2->getCookieParams()['cat']);
];
$request1 = new ServerRequest();
$request2 = $request1->withCookieParams($cookies);
$this->assertEquals($cookies, $request2->getCookieParams());
$this->assertNotSame($request2, $request1);
}
// ------------------------------------------------------------------------
@ -412,14 +47,17 @@ class ServerRequestTest extends TestCase
$this->assertEquals([], $request->getQueryParams());
}
/** @depends testGetServerRequestReadsFromRequest */
public function testWithQueryParamsCreatesNewInstance($request1)
public function testWithQueryParamsCreatesNewInstance()
{
/** @var ServerRequest $request1 */
$request2 = $request1->withQueryParams([
'guinea_pig' => 'Clyde'
]);
$this->assertNotEquals($request1->getQueryParams()['guinea_pig'], $request2->getQueryParams()['guinea_pig']);
$query = [
'cat' => 'Aggie'
];
$request1 = new ServerRequest();
$request2 = $request1->withQueryParams($query);
$this->assertEquals($query, $request2->getQueryParams());
$this->assertNotSame($request2, $request1);
}
// ------------------------------------------------------------------------
@ -431,19 +69,6 @@ class ServerRequestTest extends TestCase
$this->assertEquals([], $request->getUploadedFiles());
}
/** @backupGlobals enabled */
public function testGetUploadedFilesReturnsEmptyArrayWhenNoFilesAreUploaded()
{
$_SERVER = [
'HTTP_HOST' => 'localhost',
'HTTP_ACCEPT' => 'application/json',
'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded'
];
$_FILES = [];
$request = ServerRequest::getServerRequest();
$this->assertSame([], $request->getUploadedFiles());
}
public function testWithUploadedFilesCreatesNewInstance()
{
$uploadedFiles = [
@ -566,18 +191,17 @@ class ServerRequestTest extends TestCase
$this->assertNull($request->getParsedBody());
}
/** @depends testGetServerRequestReadsFromRequest */
public function testWithParsedBodyCreatesNewInstance($request1)
public function testWithParsedBodyCreatesNewInstance()
{
/** @var ServerRequest $request1 */
$body1 = $request1->getParsedBody();
$request2 = $request1->withParsedBody([
$body = [
'guinea_pig' => 'Clyde'
]);
$body2 = $request2->getParsedBody();
];
$this->assertNotSame($body1, $body2);
$request1 = new ServerRequest();
$request2 = $request1->withParsedBody($body);
$this->assertEquals($body, $request2->getParsedBody());
$this->assertNotSame($request2, $request1);
}
/**

View File

@ -1,19 +1,15 @@
<?php
namespace WellRESTed\Test\Unit;
namespace WellRESTed;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use WellRESTed\Dispatching\DispatcherInterface;
use WellRESTed\Message\Response;
use WellRESTed\Message\ServerRequest;
use WellRESTed\Message\Stream;
use WellRESTed\Server;
use WellRESTed\Test\TestCase;
use WellRESTed\Transmission\TransmitterInterface;
require_once __DIR__ . '/../../src/HeaderStack.php';
class ServerTest extends TestCase
{
use ProphecyTrait;
@ -204,26 +200,4 @@ class ServerTest extends TestCase
$defaultResponse
)->shouldHaveBeenCalled();
}
// -------------------------------------------------------------------------
public function testCreatesStockTransmitterByDefault()
{
$content = 'Hello, world!';
$response = (new Response())
->withBody(new Stream($content));
$server = new Server();
$server->add(function () use ($response) {
return $response;
});
ob_start();
$server->respond();
$captured = ob_get_contents();
ob_end_clean();
$this->assertEquals($content, $captured);
}
}