From 10e62cfd22ab3f9334be10855e9a5c25088b4fa8 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 13 Dec 2019 15:22:22 +0100 Subject: [PATCH] Implement PSR-7 RequestInterface support - ServerRequestInterface is not needed, parent RequestInterface is sufficient - Replaced custom PSR-7 test stubs with nyholm/psr-7 implementation - Fixed reading body contents --- composer.json | 1 + docs/executing-queries.md | 4 +- docs/reference.md | 6 +- src/Server/Helper.php | 18 +- src/Server/StandardServer.php | 6 +- tests/Server/Psr7/PsrRequestStub.php | 610 -------------------------- tests/Server/Psr7/PsrResponseStub.php | 287 ------------ tests/Server/Psr7/PsrStreamStub.php | 208 --------- tests/Server/PsrResponseTest.php | 15 +- tests/Server/RequestParsingTest.php | 81 ++-- tests/Server/StandardServerTest.php | 22 +- 11 files changed, 76 insertions(+), 1182 deletions(-) delete mode 100644 tests/Server/Psr7/PsrRequestStub.php delete mode 100644 tests/Server/Psr7/PsrResponseStub.php delete mode 100644 tests/Server/Psr7/PsrStreamStub.php diff --git a/composer.json b/composer.json index 54d22f9a9..b5761829f 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "require-dev": { "amphp/amp": "^2.3", "doctrine/coding-standard": "^6.0", + "nyholm/psr7": "^1.2", "phpbench/phpbench": "^0.14", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "0.12.18", diff --git a/docs/executing-queries.md b/docs/executing-queries.md index d3e8821d7..3c6403d43 100644 --- a/docs/executing-queries.md +++ b/docs/executing-queries.md @@ -66,11 +66,11 @@ Server also supports [PSR-7 request/response interfaces](http://www.php-fig.org/ getMethod() === 'GET') { $bodyParams = []; @@ -533,13 +535,13 @@ public function parsePsrRequest(ServerRequestInterface $request) } if (stripos($contentType[0], 'application/graphql') !== false) { - $bodyParams = ['query' => $request->getBody()->getContents()]; + $bodyParams = ['query' => (string) $request->getBody()]; } elseif (stripos($contentType[0], 'application/json') !== false) { - $bodyParams = $request->getParsedBody(); + $bodyParams = json_decode((string) $request->getBody(), true); if ($bodyParams === null) { throw new InvariantViolation( - 'PSR-7 request is expected to provide parsed body for "application/json" requests but got null' + 'Did not receive valid JSON array in PSR-7 request body with Content-Type "application/json"' ); } @@ -550,7 +552,7 @@ public function parsePsrRequest(ServerRequestInterface $request) ); } } else { - $bodyParams = $request->getParsedBody(); + parse_str((string) $request->getBody(), $bodyParams); if (! is_array($bodyParams)) { throw new RequestError('Unexpected content type: ' . Utils::printSafeJson($contentType[0])); @@ -558,10 +560,12 @@ public function parsePsrRequest(ServerRequestInterface $request) } } + parse_str(html_entity_decode($request->getUri()->getQuery()), $queryParams); + return $this->parseRequestParams( $request->getMethod(), $bodyParams, - $request->getQueryParams() + $queryParams ); } diff --git a/src/Server/StandardServer.php b/src/Server/StandardServer.php index 7b7e627b2..b3f21fa9d 100644 --- a/src/Server/StandardServer.php +++ b/src/Server/StandardServer.php @@ -9,8 +9,8 @@ use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\Promise\Promise; use GraphQL\Utils\Utils; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Throwable; use function is_array; @@ -146,7 +146,7 @@ public function executeRequest($parsedBody = null) * @api */ public function processPsrRequest( - ServerRequestInterface $request, + RequestInterface $request, ResponseInterface $response, StreamInterface $writableBodyStream ) { @@ -163,7 +163,7 @@ public function processPsrRequest( * * @api */ - public function executePsrRequest(ServerRequestInterface $request) + public function executePsrRequest(RequestInterface $request) { $parsedBody = $this->helper->parsePsrRequest($request); diff --git a/tests/Server/Psr7/PsrRequestStub.php b/tests/Server/Psr7/PsrRequestStub.php deleted file mode 100644 index 23892c04b..000000000 --- a/tests/Server/Psr7/PsrRequestStub.php +++ /dev/null @@ -1,610 +0,0 @@ -getHeaders() as $name => $values) { - * echo $name . ": " . implode(", ", $values); - * } - * - * // Emit headers iteratively: - * foreach ($message->getHeaders() as $name => $values) { - * foreach ($values as $value) { - * header(sprintf('%s: %s', $name, $value), false); - * } - * } - * - * While header names are not case-sensitive, getHeaders() will preserve the - * exact case in which headers were originally specified. - * - * @return string[][] Returns an associative array of the message's headers. Each - * key MUST be a header name, and each value MUST be an array of strings - * for that header. - */ - public function getHeaders() - { - throw new \Exception('Not implemented'); - } - - /** - * Checks if a header exists by the given case-insensitive name. - * - * @param string $name Case-insensitive header field name. - * @return bool Returns true if any header names match the given header - * name using a case-insensitive string comparison. Returns false if - * no matching header name is found in the message. - */ - public function hasHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves a message header value by the given case-insensitive name. - * - * This method returns an array of all the header values of the given - * case-insensitive header name. - * - * If the header does not appear in the message, this method MUST return an - * empty array. - * - * @param string $name Case-insensitive header field name. - * @return string[] An array of string values as provided for the given - * header. If the header does not appear in the message, this method MUST - * return an empty array. - */ - public function getHeader($name) - { - $name = strtolower($name); - - return $this->headers[$name] ?? []; - } - - /** - * Retrieves a comma-separated string of the values for a single header. - * - * This method returns all of the header values of the given - * case-insensitive header name as a string concatenated together using - * a comma. - * - * NOTE: Not all header values may be appropriately represented using - * comma concatenation. For such headers, use getHeader() instead - * and supply your own delimiter when concatenating. - * - * If the header does not appear in the message, this method MUST return - * an empty string. - * - * @param string $name Case-insensitive header field name. - * @return string A string of values as provided for the given header - * concatenated together using a comma. If the header does not appear in - * the message, this method MUST return an empty string. - */ - public function getHeaderLine($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the provided value replacing the specified header. - * - * While header names are case-insensitive, the casing of the header will - * be preserved by this function, and returned from getHeaders(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new and/or updated header and value. - * - * @param string $name Case-insensitive header field name. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withHeader($name, $value) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified header appended with the given value. - * - * Existing values for the specified header will be maintained. The new - * value(s) will be appended to the existing list. If the header did not - * exist previously, it will be added. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new header and/or value. - * - * @param string $name Case-insensitive header field name to add. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withAddedHeader($name, $value) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance without the specified header. - * - * Header resolution MUST be done without case-sensitivity. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the named header. - * - * @param string $name Case-insensitive header field name to remove. - * @return static - */ - public function withoutHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Gets the body of the message. - * - * @return StreamInterface Returns the body as a stream. - */ - public function getBody() - { - return $this->body; - } - - /** - * Return an instance with the specified message body. - * - * The body MUST be a StreamInterface object. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return a new instance that has the - * new body stream. - * - * @param StreamInterface $body Body. - * @return static - * @throws \InvalidArgumentException When the body is not valid. - */ - public function withBody(StreamInterface $body) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves the message's request target. - * - * Retrieves the message's request-target either as it will appear (for - * clients), as it appeared at request (for servers), or as it was - * specified for the instance (see withRequestTarget()). - * - * In most cases, this will be the origin-form of the composed URI, - * unless a value was provided to the concrete implementation (see - * withRequestTarget() below). - * - * If no URI is available, and no request-target has been specifically - * provided, this method MUST return the string "/". - * - * @return string - */ - public function getRequestTarget() - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specific request-target. - * - * If the request needs a non-origin-form request-target — e.g., for - * specifying an absolute-form, authority-form, or asterisk-form — - * this method may be used to create an instance with the specified - * request-target, verbatim. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * changed request target. - * - * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various - * request-target forms allowed in request messages) - * @param mixed $requestTarget - * @return static - */ - public function withRequestTarget($requestTarget) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves the HTTP method of the request. - * - * @return string Returns the request method. - */ - public function getMethod() - { - return $this->method; - } - - /** - * Return an instance with the provided HTTP method. - * - * While HTTP method names are typically all uppercase characters, HTTP - * method names are case-sensitive and thus implementations SHOULD NOT - * modify the given string. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * changed request method. - * - * @param string $method Case-sensitive method. - * @return static - * @throws \InvalidArgumentException for invalid HTTP methods. - */ - public function withMethod($method) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves the URI instance. - * - * This method MUST return a UriInterface instance. - * - * @link http://tools.ietf.org/html/rfc3986#section-4.3 - * @return UriInterface Returns a UriInterface instance - * representing the URI of the request. - */ - public function getUri() - { - throw new \Exception('Not implemented'); - } - - /** - * Returns an instance with the provided URI. - * - * This method MUST update the Host header of the returned request by - * default if the URI contains a host component. If the URI does not - * contain a host component, any pre-existing Host header MUST be carried - * over to the returned request. - * - * You can opt-in to preserving the original state of the Host header by - * setting `$preserveHost` to `true`. When `$preserveHost` is set to - * `true`, this method interacts with the Host header in the following ways: - * - * - If the Host header is missing or empty, and the new URI contains - * a host component, this method MUST update the Host header in the returned - * request. - * - If the Host header is missing or empty, and the new URI does not contain a - * host component, this method MUST NOT update the Host header in the returned - * request. - * - If a Host header is present and non-empty, this method MUST NOT update - * the Host header in the returned request. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new UriInterface instance. - * - * @link http://tools.ietf.org/html/rfc3986#section-4.3 - * @param UriInterface $uri New request URI to use. - * @param bool $preserveHost Preserve the original state of the Host header. - * @return static - */ - public function withUri(UriInterface $uri, $preserveHost = false) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve server parameters. - * - * Retrieves data related to the incoming request environment, - * typically derived from PHP's $_SERVER superglobal. The data IS NOT - * REQUIRED to originate from $_SERVER. - * - * @return array - */ - public function getServerParams() - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve cookies. - * - * Retrieves cookies sent by the client to the server. - * - * The data MUST be compatible with the structure of the $_COOKIE - * superglobal. - * - * @return array - */ - public function getCookieParams() - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified cookies. - * - * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST - * be compatible with the structure of $_COOKIE. Typically, this data will - * be injected at instantiation. - * - * This method MUST NOT update the related Cookie header of the request - * instance, nor related values in the server params. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated cookie values. - * - * @param array $cookies Array of key/value pairs representing cookies. - * @return static - */ - public function withCookieParams(array $cookies) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve query string arguments. - * - * Retrieves the deserialized query string arguments, if any. - * - * Note: the query params might not be in sync with the URI or server - * params. If you need to ensure you are only getting the original - * values, you may need to parse the query string from `getUri()->getQuery()` - * or from the `QUERY_STRING` server param. - * - * @return array - */ - public function getQueryParams() - { - return $this->queryParams; - } - - /** - * Return an instance with the specified query string arguments. - * - * These values SHOULD remain immutable over the course of the incoming - * request. They MAY be injected during instantiation, such as from PHP's - * $_GET superglobal, or MAY be derived from some other value such as the - * URI. In cases where the arguments are parsed from the URI, the data - * MUST be compatible with what PHP's parse_str() would return for - * purposes of how duplicate query parameters are handled, and how nested - * sets are handled. - * - * Setting query string arguments MUST NOT change the URI stored by the - * request, nor the values in the server params. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated query string arguments. - * - * @param array $query Array of query string arguments, typically from - * $_GET. - * @return static - */ - public function withQueryParams(array $query) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve normalized file upload data. - * - * This method returns upload metadata in a normalized tree, with each leaf - * an instance of Psr\Http\Message\UploadedFileInterface. - * - * These values MAY be prepared from $_FILES or the message body during - * instantiation, or MAY be injected via withUploadedFiles(). - * - * @return array An array tree of UploadedFileInterface instances; an empty - * array MUST be returned if no data is present. - */ - public function getUploadedFiles() - { - throw new \Exception('Not implemented'); - } - - /** - * Create a new instance with the specified uploaded files. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated body parameters. - * - * @param array $uploadedFiles An array tree of UploadedFileInterface instances. - * @return static - * @throws \InvalidArgumentException if an invalid structure is provided. - */ - public function withUploadedFiles(array $uploadedFiles) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve any parameters provided in the request body. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, this method MUST - * return the contents of $_POST. - * - * Otherwise, this method may return any results of deserializing - * the request body content; as parsing returns structured content, the - * potential types MUST be arrays or objects only. A null value indicates - * the absence of body content. - * - * @return null|array|object The deserialized body parameters, if any. - * These will typically be an array or object. - */ - public function getParsedBody() - { - return $this->parsedBody; - } - - /** - * Return an instance with the specified body parameters. - * - * These MAY be injected during instantiation. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, use this method - * ONLY to inject the contents of $_POST. - * - * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of - * deserializing the request body content. Deserialization/parsing returns - * structured data, and, as such, this method ONLY accepts arrays or objects, - * or a null value if nothing was available to parse. - * - * As an example, if content negotiation determines that the request data - * is a JSON payload, this method could be used to create a request - * instance with the deserialized parameters. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated body parameters. - * - * @param null|array|object $data The deserialized body data. This will - * typically be in an array or object. - * @return static - * @throws \InvalidArgumentException if an unsupported argument type is - * provided. - */ - public function withParsedBody($data) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve attributes derived from the request. - * - * The request "attributes" may be used to allow injection of any - * parameters derived from the request: e.g., the results of path - * match operations; the results of decrypting cookies; the results of - * deserializing non-form-encoded message bodies; etc. Attributes - * will be application and request specific, and CAN be mutable. - * - * @return array Attributes derived from the request. - */ - public function getAttributes() - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve a single derived request attribute. - * - * Retrieves a single derived request attribute as described in - * getAttributes(). If the attribute has not been previously set, returns - * the default value as provided. - * - * This method obviates the need for a hasAttribute() method, as it allows - * specifying a default value to return if the attribute is not found. - * - * @see getAttributes() - * @param string $name The attribute name. - * @param mixed $default Default value to return if the attribute does not exist. - * @return mixed - */ - public function getAttribute($name, $default = null) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified derived request attribute. - * - * This method allows setting a single derived request attribute as - * described in getAttributes(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @param mixed $value The value of the attribute. - * @return static - */ - public function withAttribute($name, $value) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance that removes the specified derived request attribute. - * - * This method allows removing a single derived request attribute as - * described in getAttributes(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @return static - */ - public function withoutAttribute($name) - { - throw new \Exception('Not implemented'); - } -} diff --git a/tests/Server/Psr7/PsrResponseStub.php b/tests/Server/Psr7/PsrResponseStub.php deleted file mode 100644 index bac2ffb23..000000000 --- a/tests/Server/Psr7/PsrResponseStub.php +++ /dev/null @@ -1,287 +0,0 @@ -getHeaders() as $name => $values) { - * echo $name . ": " . implode(", ", $values); - * } - * - * // Emit headers iteratively: - * foreach ($message->getHeaders() as $name => $values) { - * foreach ($values as $value) { - * header(sprintf('%s: %s', $name, $value), false); - * } - * } - * - * While header names are not case-sensitive, getHeaders() will preserve the - * exact case in which headers were originally specified. - * - * @return string[][] Returns an associative array of the message's headers. Each - * key MUST be a header name, and each value MUST be an array of strings - * for that header. - */ - public function getHeaders() - { - throw new \Exception('Not implemented'); - } - - /** - * Checks if a header exists by the given case-insensitive name. - * - * @param string $name Case-insensitive header field name. - * @return bool Returns true if any header names match the given header - * name using a case-insensitive string comparison. Returns false if - * no matching header name is found in the message. - */ - public function hasHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves a message header value by the given case-insensitive name. - * - * This method returns an array of all the header values of the given - * case-insensitive header name. - * - * If the header does not appear in the message, this method MUST return an - * empty array. - * - * @param string $name Case-insensitive header field name. - * @return string[] An array of string values as provided for the given - * header. If the header does not appear in the message, this method MUST - * return an empty array. - */ - public function getHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves a comma-separated string of the values for a single header. - * - * This method returns all of the header values of the given - * case-insensitive header name as a string concatenated together using - * a comma. - * - * NOTE: Not all header values may be appropriately represented using - * comma concatenation. For such headers, use getHeader() instead - * and supply your own delimiter when concatenating. - * - * If the header does not appear in the message, this method MUST return - * an empty string. - * - * @param string $name Case-insensitive header field name. - * @return string A string of values as provided for the given header - * concatenated together using a comma. If the header does not appear in - * the message, this method MUST return an empty string. - */ - public function getHeaderLine($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the provided value replacing the specified header. - * - * While header names are case-insensitive, the casing of the header will - * be preserved by this function, and returned from getHeaders(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new and/or updated header and value. - * - * @param string $name Case-insensitive header field name. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withHeader($name, $value) - { - $tmp = clone $this; - $tmp->headers[$name][] = $value; - - return $tmp; - } - - /** - * Return an instance with the specified header appended with the given value. - * - * Existing values for the specified header will be maintained. The new - * value(s) will be appended to the existing list. If the header did not - * exist previously, it will be added. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new header and/or value. - * - * @param string $name Case-insensitive header field name to add. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withAddedHeader($name, $value) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance without the specified header. - * - * Header resolution MUST be done without case-sensitivity. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the named header. - * - * @param string $name Case-insensitive header field name to remove. - * @return static - */ - public function withoutHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Gets the body of the message. - * - * @return StreamInterface Returns the body as a stream. - */ - public function getBody() - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified message body. - * - * The body MUST be a StreamInterface object. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return a new instance that has the - * new body stream. - * - * @param StreamInterface $body Body. - * @return static - * @throws \InvalidArgumentException When the body is not valid. - */ - public function withBody(StreamInterface $body) - { - $tmp = clone $this; - $tmp->body = $body; - - return $tmp; - } - - /** - * Gets the response status code. - * - * The status code is a 3-digit integer result code of the server's attempt - * to understand and satisfy the request. - * - * @return int Status code. - */ - public function getStatusCode() - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified status code and, optionally, reason phrase. - * - * If no reason phrase is specified, implementations MAY choose to default - * to the RFC 7231 or IANA recommended reason phrase for the response's - * status code. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated status and reason phrase. - * - * @link http://tools.ietf.org/html/rfc7231#section-6 - * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml - * @param int $code The 3-digit integer result code to set. - * @param string $reasonPhrase The reason phrase to use with the - * provided status code; if none is provided, implementations MAY - * use the defaults as suggested in the HTTP specification. - * @return static - * @throws \InvalidArgumentException For invalid status code arguments. - */ - public function withStatus($code, $reasonPhrase = '') - { - $tmp = clone $this; - $tmp->statusCode = $code; - - return $tmp; - } - - /** - * Gets the response reason phrase associated with the status code. - * - * Because a reason phrase is not a required element in a response - * status line, the reason phrase value MAY be null. Implementations MAY - * choose to return the default RFC 7231 recommended reason phrase (or those - * listed in the IANA HTTP Status Code Registry) for the response's - * status code. - * - * @link http://tools.ietf.org/html/rfc7231#section-6 - * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml - * @return string Reason phrase; must return an empty string if none present. - */ - public function getReasonPhrase() - { - throw new \Exception('Not implemented'); - } -} diff --git a/tests/Server/Psr7/PsrStreamStub.php b/tests/Server/Psr7/PsrStreamStub.php deleted file mode 100644 index 1865dfedd..000000000 --- a/tests/Server/Psr7/PsrStreamStub.php +++ /dev/null @@ -1,208 +0,0 @@ -content; - } - - /** - * Closes the stream and any underlying resources. - * - * @return void - */ - public function close() - { - throw new \Exception('Not implemented'); - } - - /** - * Separates any underlying resources from the stream. - * - * After the stream has been detached, the stream is in an unusable state. - * - * @return resource|null Underlying PHP stream, if any - */ - public function detach() - { - throw new \Exception('Not implemented'); - } - - /** - * Get the size of the stream if known. - * - */ - public function getSize() : ?int - { - return $this->content === null ? null : strlen($this->content); - } - - /** - * Returns the current position of the file read/write pointer - * - * @return int Position of the file pointer - * @throws \RuntimeException on error. - */ - public function tell() - { - throw new \Exception('Not implemented'); - } - - /** - * Returns true if the stream is at the end of the stream. - * - * @return bool - */ - public function eof() - { - throw new \Exception('Not implemented'); - } - - /** - * Returns whether or not the stream is seekable. - * - * @return bool - */ - public function isSeekable() - { - throw new \Exception('Not implemented'); - } - - /** - * Seek to a position in the stream. - * - * @link http://www.php.net/manual/en/function.fseek.php - * @param int $offset Stream offset - * @param int $whence Specifies how the cursor position will be calculated - * based on the seek offset. Valid values are identical to the built-in - * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to - * offset bytes SEEK_CUR: Set position to current location plus offset - * SEEK_END: Set position to end-of-stream plus offset. - * @throws \RuntimeException on failure. - */ - public function seek($offset, $whence = SEEK_SET) - { - throw new \Exception('Not implemented'); - } - - /** - * Seek to the beginning of the stream. - * - * If the stream is not seekable, this method will raise an exception; - * otherwise, it will perform a seek(0). - * - * @see seek() - * @link http://www.php.net/manual/en/function.fseek.php - * @throws \RuntimeException on failure. - */ - public function rewind() - { - throw new \Exception('Not implemented'); - } - - /** - * Returns whether or not the stream is writable. - * - * @return bool - */ - public function isWritable() - { - return true; - } - - /** - * Write data to the stream. - * - * @param string $string The string that is to be written. - * @return int Returns the number of bytes written to the stream. - * @throws \RuntimeException on failure. - */ - public function write($string) - { - $this->content = $string; - - return strlen($string); - } - - /** - * Returns whether or not the stream is readable. - * - * @return bool - */ - public function isReadable() - { - throw new \Exception('Not implemented'); - } - - /** - * Read data from the stream. - * - * @param int $length Read up to $length bytes from the object and return - * them. Fewer than $length bytes may be returned if underlying stream - * call returns fewer bytes. - * @return string Returns the data read from the stream, or an empty string - * if no bytes are available. - * @throws \RuntimeException if an error occurs. - */ - public function read($length) - { - throw new \Exception('Not implemented'); - } - - /** - * Returns the remaining contents in a string - * - * @return string - * @throws \RuntimeException if unable to read or an error occurs while - * reading. - */ - public function getContents() : string - { - return $this->content; - } - - /** - * Get stream metadata as an associative array or retrieve a specific key. - * - * The keys returned are identical to the keys returned from PHP's - * stream_get_meta_data() function. - * - * @link http://php.net/manual/en/function.stream-get-meta-data.php - * @param string $key Specific metadata to retrieve. - * @return array|mixed|null Returns an associative array if no key is - * provided. Returns a specific key value if a key is provided and the - * value is found, or null if the key is not found. - */ - public function getMetadata($key = null) - { - throw new \Exception('Not implemented'); - } -} diff --git a/tests/Server/PsrResponseTest.php b/tests/Server/PsrResponseTest.php index 5a2eab525..6edbf3014 100644 --- a/tests/Server/PsrResponseTest.php +++ b/tests/Server/PsrResponseTest.php @@ -6,24 +6,23 @@ use GraphQL\Executor\ExecutionResult; use GraphQL\Server\Helper; -use GraphQL\Tests\Server\Psr7\PsrResponseStub; -use GraphQL\Tests\Server\Psr7\PsrStreamStub; +use Nyholm\Psr7\Response; +use Nyholm\Psr7\Stream; use PHPUnit\Framework\TestCase; use function json_encode; -class PsrResponseTest extends TestCase +final class PsrResponseTest extends TestCase { public function testConvertsResultToPsrResponse() : void { $result = new ExecutionResult(['key' => 'value']); - $stream = new PsrStreamStub(); - $psrResponse = new PsrResponseStub(); + $stream = Stream::create(); + $psrResponse = new Response(); $helper = new Helper(); - /** @var PsrResponseStub $resp */ $resp = $helper->toPsrResponse($result, $psrResponse, $stream); - self::assertSame(json_encode($result), $resp->body->content); - self::assertSame(['Content-Type' => ['application/json']], $resp->headers); + self::assertSame(json_encode($result), (string) $resp->getBody()); + self::assertSame(['Content-Type' => ['application/json']], $resp->getHeaders()); } } diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index a926b1263..16df93aca 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -8,10 +8,12 @@ use GraphQL\Server\Helper; use GraphQL\Server\OperationParams; use GraphQL\Server\RequestError; -use GraphQL\Tests\Server\Psr7\PsrRequestStub; -use GraphQL\Tests\Server\Psr7\PsrStreamStub; +use InvalidArgumentException; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Stream; +use Nyholm\Psr7\Uri; use PHPUnit\Framework\TestCase; -use function json_decode; +use function http_build_query; use function json_encode; class RequestParsingTest extends TestCase @@ -56,22 +58,12 @@ private function parseRawRequest($contentType, $content, string $method = 'POST' */ private function parsePsrRequest($contentType, $content, string $method = 'POST') { - $psrRequestBody = new PsrStreamStub(); - $psrRequestBody->content = $content; - - $psrRequest = new PsrRequestStub(); - $psrRequest->headers['content-type'] = [$contentType]; - $psrRequest->method = $method; - $psrRequest->body = $psrRequestBody; - - if ($contentType === 'application/json') { - $parsedBody = json_decode($content, true); - $parsedBody = $parsedBody === false ? null : $parsedBody; - } else { - $parsedBody = null; - } - - $psrRequest->parsedBody = $parsedBody; + $psrRequest = new Request( + $method, + '', + ['Content-Type' => $contentType], + Stream::create($content) + ); $helper = new Helper(); @@ -106,7 +98,7 @@ private static function assertValidOperationParams( public function testParsesUrlencodedRequest() : void { $query = '{my query}'; - $variables = ['test' => 1, 'test2' => 2]; + $variables = ['test' => '1', 'test2' => '2']; $operation = 'op'; $post = [ @@ -150,20 +142,22 @@ private function parseRawFormUrlencodedRequest($postValue) */ private function parsePsrFormUrlEncodedRequest($postValue) { - $psrRequest = new PsrRequestStub(); - $psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded']; - $psrRequest->method = 'POST'; - $psrRequest->parsedBody = $postValue; - $helper = new Helper(); - return $helper->parsePsrRequest($psrRequest); + return $helper->parsePsrRequest( + new Request( + 'POST', + '', + ['Content-Type' => 'application/x-www-form-urlencoded'], + http_build_query($postValue) + ) + ); } public function testParsesGetRequest() : void { $query = '{my query}'; - $variables = ['test' => 1, 'test2' => 2]; + $variables = ['test' => '1', 'test2' => '2']; $operation = 'op'; $get = [ @@ -204,21 +198,19 @@ private function parseRawGetRequest($getValue) * * @return OperationParams[]|OperationParams */ - private function parsePsrGetRequest($getValue) + private function parsePsrGetRequest(array $getValue) { - $psrRequest = new PsrRequestStub(); - $psrRequest->method = 'GET'; - $psrRequest->queryParams = $getValue; - $helper = new Helper(); - return $helper->parsePsrRequest($psrRequest); + return $helper->parsePsrRequest( + new Request('GET', (new Uri())->withQuery(http_build_query($getValue))) + ); } public function testParsesMultipartFormdataRequest() : void { $query = '{my query}'; - $variables = ['test' => 1, 'test2' => 2]; + $variables = ['test' => '1', 'test2' => '2']; $operation = 'op'; $post = [ @@ -262,14 +254,16 @@ private function parseRawMultipartFormDataRequest($postValue) */ private function parsePsrMultipartFormDataRequest($postValue) { - $psrRequest = new PsrRequestStub(); - $psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary']; - $psrRequest->method = 'POST'; - $psrRequest->parsedBody = $postValue; - $helper = new Helper(); - return $helper->parsePsrRequest($psrRequest); + return $helper->parsePsrRequest( + new Request( + 'POST', + '', + ['Content-Type' => 'multipart/form-data; boundary=----FormBoundary'], + http_build_query($postValue) + ) + ); } public function testParsesJSONRequest() : void @@ -415,7 +409,7 @@ public function testFailsParsingInvalidRawJsonRequestPsr() : void $body = 'not really{} a json'; $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "application/json" requests but got null'); + $this->expectExceptionMessage('Did not receive valid JSON array in PSR-7 request body with Content-Type "application/json"'); $this->parsePsrRequest('application/json', $body); } @@ -427,7 +421,7 @@ public function testFailsParsingNonPreParsedPsrRequest() : void } catch (InvariantViolation $e) { // Expecting parsing exception to be thrown somewhere else: self::assertEquals( - 'PSR-7 request is expected to provide parsed body for "application/json" requests but got null', + 'Did not receive valid JSON array in PSR-7 request body with Content-Type "application/json"', $e->getMessage() ); } @@ -483,8 +477,7 @@ public function testFailsWithMissingContentTypeRaw() : void public function testFailsWithMissingContentTypePsr() : void { - $this->expectException(RequestError::class); - $this->expectExceptionMessage('Missing "Content-Type" header'); + $this->expectException(InvalidArgumentException::class); $this->parsePsrRequest(null, 'test'); } diff --git a/tests/Server/StandardServerTest.php b/tests/Server/StandardServerTest.php index 516bf687d..57c65a2b4 100644 --- a/tests/Server/StandardServerTest.php +++ b/tests/Server/StandardServerTest.php @@ -9,7 +9,9 @@ use GraphQL\Server\ServerConfig; use GraphQL\Server\StandardServer; use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; -use GraphQL\Tests\Server\Psr7\PsrRequestStub; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Stream; +use Psr\Http\Message\RequestInterface; use function json_encode; class StandardServerTest extends ServerTestCase @@ -61,18 +63,18 @@ public function testSimplePsrRequestExecution() : void 'data' => ['f1' => 'f1'], ]; - $request = $this->preparePsrRequest('application/json', $body); + $request = $this->preparePsrRequest('application/json', json_encode($body)); $this->assertPsrRequestEquals($expected, $request); } - private function preparePsrRequest($contentType, $parsedBody) + private function preparePsrRequest($contentType, $body) : RequestInterface { - $psrRequest = new PsrRequestStub(); - $psrRequest->headers['content-type'] = [$contentType]; - $psrRequest->method = 'POST'; - $psrRequest->parsedBody = $parsedBody; - - return $psrRequest; + return new Request( + 'POST', + '', + ['Content-Type' => $contentType], + $body + ); } private function assertPsrRequestEquals($expected, $request) @@ -103,7 +105,7 @@ public function testMultipleOperationPsrRequestExecution() : void 'data' => ['f1' => 'f1'], ]; - $request = $this->preparePsrRequest('application/json', $body); + $request = $this->preparePsrRequest('application/json', json_encode($body)); $this->assertPsrRequestEquals($expected, $request); } }