CS /Server

This commit is contained in:
Simon Podlipsky 2018-07-10 01:19:15 +03:00
parent c1a62fdb05
commit ec2ff0d4bf
No known key found for this signature in database
GPG Key ID: 725C2BD962B42663
5 changed files with 227 additions and 214 deletions

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Server; namespace GraphQL\Server;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -17,13 +20,23 @@ use GraphQL\Utils\Utils;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use function file_get_contents;
use function header;
use function is_array;
use function is_callable;
use function is_string;
use function json_decode;
use function json_encode;
use function json_last_error;
use function json_last_error_msg;
use function sprintf;
use function stripos;
/** /**
* Contains functionality that could be re-used by various server implementations * Contains functionality that could be re-used by various server implementations
*/ */
class Helper class Helper
{ {
/** /**
* Parses HTTP request using PHP globals and returns GraphQL OperationParams * Parses HTTP request using PHP globals and returns GraphQL OperationParams
* contained in this request. For batched requests it returns an array of OperationParams. * contained in this request. For batched requests it returns an array of OperationParams.
@ -40,43 +53,45 @@ class Helper
* For PSR-7 request parsing use `parsePsrRequest()` instead. * For PSR-7 request parsing use `parsePsrRequest()` instead.
* *
* @api * @api
* @param callable|null $readRawBodyFn
* @return OperationParams|OperationParams[] * @return OperationParams|OperationParams[]
* @throws RequestError * @throws RequestError
*/ */
public function parseHttpRequest(callable $readRawBodyFn = null) public function parseHttpRequest(?callable $readRawBodyFn = null)
{ {
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null; $method = $_SERVER['REQUEST_METHOD'] ?? null;
$bodyParams = []; $bodyParams = [];
$urlParams = $_GET; $urlParams = $_GET;
if ($method === 'POST') { if ($method === 'POST') {
$contentType = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : null; $contentType = $_SERVER['CONTENT_TYPE'] ?? null;
if ($contentType === null) {
throw new RequestError('Missing "Content-Type" header');
}
if (stripos($contentType, 'application/graphql') !== false) { if (stripos($contentType, 'application/graphql') !== false) {
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody(); $rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
$bodyParams = ['query' => $rawBody ?: '']; $bodyParams = ['query' => $rawBody ?: ''];
} else if (stripos($contentType, 'application/json') !== false) { } elseif (stripos($contentType, 'application/json') !== false) {
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody(); $rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
$bodyParams = json_decode($rawBody ?: '', true); $bodyParams = json_decode($rawBody ?: '', true);
if (json_last_error()) { if (json_last_error()) {
throw new RequestError("Could not parse JSON: " . json_last_error_msg()); throw new RequestError('Could not parse JSON: ' . json_last_error_msg());
} }
if (!is_array($bodyParams)) {
if (! is_array($bodyParams)) {
throw new RequestError( throw new RequestError(
"GraphQL Server expects JSON object or array, but got " . 'GraphQL Server expects JSON object or array, but got ' .
Utils::printSafeJson($bodyParams) Utils::printSafeJson($bodyParams)
); );
} }
} else if (stripos($contentType, 'application/x-www-form-urlencoded') !== false) { } elseif (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
$bodyParams = $_POST; $bodyParams = $_POST;
} else if (stripos($contentType, 'multipart/form-data') !== false) { } elseif (stripos($contentType, 'multipart/form-data') !== false) {
$bodyParams = $_POST; $bodyParams = $_POST;
} else if (null === $contentType) {
throw new RequestError('Missing "Content-Type" header');
} else { } else {
throw new RequestError("Unexpected content type: " . Utils::printSafeJson($contentType)); throw new RequestError('Unexpected content type: ' . Utils::printSafeJson($contentType));
} }
} }
@ -90,9 +105,9 @@ class Helper
* Returned value is a suitable input for `executeOperation` or `executeBatch` (if array) * Returned value is a suitable input for `executeOperation` or `executeBatch` (if array)
* *
* @api * @api
* @param string $method * @param string $method
* @param array $bodyParams * @param mixed[] $bodyParams
* @param array $queryParams * @param mixed[] $queryParams
* @return OperationParams|OperationParams[] * @return OperationParams|OperationParams[]
* @throws RequestError * @throws RequestError
*/ */
@ -100,11 +115,11 @@ class Helper
{ {
if ($method === 'GET') { if ($method === 'GET') {
$result = OperationParams::create($queryParams, true); $result = OperationParams::create($queryParams, true);
} else if ($method === 'POST') { } elseif ($method === 'POST') {
if (isset($bodyParams[0])) { if (isset($bodyParams[0])) {
$result = []; $result = [];
foreach ($bodyParams as $index => $entry) { foreach ($bodyParams as $index => $entry) {
$op = OperationParams::create($entry); $op = OperationParams::create($entry);
$result[] = $op; $result[] = $op;
} }
} else { } else {
@ -113,6 +128,7 @@ class Helper
} else { } else {
throw new RequestError('HTTP Method "' . $method . '" is not supported'); throw new RequestError('HTTP Method "' . $method . '" is not supported');
} }
return $result; return $result;
} }
@ -121,44 +137,47 @@ class Helper
* if params are invalid (or empty array when params are valid) * if params are invalid (or empty array when params are valid)
* *
* @api * @api
* @param OperationParams $params
* @return Error[] * @return Error[]
*/ */
public function validateOperationParams(OperationParams $params) public function validateOperationParams(OperationParams $params)
{ {
$errors = []; $errors = [];
if (!$params->query && !$params->queryId) { if (! $params->query && ! $params->queryId) {
$errors[] = new RequestError('GraphQL Request must include at least one of those two parameters: "query" or "queryId"'); $errors[] = new RequestError('GraphQL Request must include at least one of those two parameters: "query" or "queryId"');
} }
if ($params->query && $params->queryId) { if ($params->query && $params->queryId) {
$errors[] = new RequestError('GraphQL Request parameters "query" and "queryId" are mutually exclusive'); $errors[] = new RequestError('GraphQL Request parameters "query" and "queryId" are mutually exclusive');
} }
if ($params->query !== null && (!is_string($params->query) || empty($params->query))) { if ($params->query !== null && (! is_string($params->query) || empty($params->query))) {
$errors[] = new RequestError( $errors[] = new RequestError(
'GraphQL Request parameter "query" must be string, but got ' . 'GraphQL Request parameter "query" must be string, but got ' .
Utils::printSafeJson($params->query) Utils::printSafeJson($params->query)
); );
} }
if ($params->queryId !== null && (!is_string($params->queryId) || empty($params->queryId))) {
if ($params->queryId !== null && (! is_string($params->queryId) || empty($params->queryId))) {
$errors[] = new RequestError( $errors[] = new RequestError(
'GraphQL Request parameter "queryId" must be string, but got ' . 'GraphQL Request parameter "queryId" must be string, but got ' .
Utils::printSafeJson($params->queryId) Utils::printSafeJson($params->queryId)
); );
} }
if ($params->operation !== null && (!is_string($params->operation) || empty($params->operation))) { if ($params->operation !== null && (! is_string($params->operation) || empty($params->operation))) {
$errors[] = new RequestError( $errors[] = new RequestError(
'GraphQL Request parameter "operation" must be string, but got ' . 'GraphQL Request parameter "operation" must be string, but got ' .
Utils::printSafeJson($params->operation) Utils::printSafeJson($params->operation)
); );
} }
if ($params->variables !== null && (!is_array($params->variables) || isset($params->variables[0]))) {
if ($params->variables !== null && (! is_array($params->variables) || isset($params->variables[0]))) {
$errors[] = new RequestError( $errors[] = new RequestError(
'GraphQL Request parameter "variables" must be object or JSON string parsed to object, but got ' . 'GraphQL Request parameter "variables" must be object or JSON string parsed to object, but got ' .
Utils::printSafeJson($params->getOriginalInput('variables')) Utils::printSafeJson($params->getOriginalInput('variables'))
); );
} }
return $errors; return $errors;
} }
@ -167,15 +186,13 @@ class Helper
* (or promise when promise adapter is different from SyncPromiseAdapter) * (or promise when promise adapter is different from SyncPromiseAdapter)
* *
* @api * @api
* @param ServerConfig $config
* @param OperationParams $op
* *
* @return ExecutionResult|Promise * @return ExecutionResult|Promise
*/ */
public function executeOperation(ServerConfig $config, OperationParams $op) public function executeOperation(ServerConfig $config, OperationParams $op)
{ {
$promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter(); $promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter();
$result = $this->promiseToExecuteOperation($promiseAdapter, $config, $op); $result = $this->promiseToExecuteOperation($promiseAdapter, $config, $op);
if ($promiseAdapter instanceof SyncPromiseAdapter) { if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result); $result = $promiseAdapter->wait($result);
@ -189,14 +206,13 @@ class Helper
* (thus, effectively batching deferreds|promises of all queries at once) * (thus, effectively batching deferreds|promises of all queries at once)
* *
* @api * @api
* @param ServerConfig $config
* @param OperationParams[] $operations * @param OperationParams[] $operations
* @return ExecutionResult|ExecutionResult[]|Promise * @return ExecutionResult|ExecutionResult[]|Promise
*/ */
public function executeBatch(ServerConfig $config, array $operations) public function executeBatch(ServerConfig $config, array $operations)
{ {
$promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter(); $promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter();
$result = []; $result = [];
foreach ($operations as $operation) { foreach ($operations as $operation) {
$result[] = $this->promiseToExecuteOperation($promiseAdapter, $config, $operation, true); $result[] = $this->promiseToExecuteOperation($promiseAdapter, $config, $operation, true);
@ -208,32 +224,39 @@ class Helper
if ($promiseAdapter instanceof SyncPromiseAdapter) { if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result); $result = $promiseAdapter->wait($result);
} }
return $result; return $result;
} }
/** /**
* @param PromiseAdapter $promiseAdapter
* @param ServerConfig $config
* @param OperationParams $op
* @param bool $isBatch * @param bool $isBatch
* @return Promise * @return Promise
*/ */
private function promiseToExecuteOperation(PromiseAdapter $promiseAdapter, ServerConfig $config, OperationParams $op, $isBatch = false) private function promiseToExecuteOperation(
{ PromiseAdapter $promiseAdapter,
ServerConfig $config,
OperationParams $op,
$isBatch = false
) {
try { try {
if (!$config->getSchema()) { if (! $config->getSchema()) {
throw new InvariantViolation("Schema is required for the server"); throw new InvariantViolation('Schema is required for the server');
} }
if ($isBatch && !$config->getQueryBatching()) {
throw new RequestError("Batched queries are not supported by this server"); if ($isBatch && ! $config->getQueryBatching()) {
throw new RequestError('Batched queries are not supported by this server');
} }
$errors = $this->validateOperationParams($op); $errors = $this->validateOperationParams($op);
if (!empty($errors)) { if (! empty($errors)) {
$errors = Utils::map($errors, function(RequestError $err) { $errors = Utils::map(
return Error::createLocatedError($err, null, null); $errors,
}); function (RequestError $err) {
return Error::createLocatedError($err, null, null);
}
);
return $promiseAdapter->createFulfilled( return $promiseAdapter->createFulfilled(
new ExecutionResult(null, $errors) new ExecutionResult(null, $errors)
); );
@ -241,13 +264,13 @@ class Helper
$doc = $op->queryId ? $this->loadPersistedQuery($config, $op) : $op->query; $doc = $op->queryId ? $this->loadPersistedQuery($config, $op) : $op->query;
if (!$doc instanceof DocumentNode) { if (! $doc instanceof DocumentNode) {
$doc = Parser::parse($doc); $doc = Parser::parse($doc);
} }
$operationType = AST::getOperation($doc, $op->operation); $operationType = AST::getOperation($doc, $op->operation);
if ($op->isReadOnly() && $operationType !== 'query') { if ($operationType !== 'query' && $op->isReadOnly()) {
throw new RequestError("GET supports only query operation"); throw new RequestError('GET supports only query operation');
} }
$result = GraphQL::promiseToExecute( $result = GraphQL::promiseToExecute(
@ -261,7 +284,6 @@ class Helper
$config->getFieldResolver(), $config->getFieldResolver(),
$this->resolveValidationRules($config, $op, $doc, $operationType) $this->resolveValidationRules($config, $op, $doc, $operationType)
); );
} catch (RequestError $e) { } catch (RequestError $e) {
$result = $promiseAdapter->createFulfilled( $result = $promiseAdapter->createFulfilled(
new ExecutionResult(null, [Error::createLocatedError($e)]) new ExecutionResult(null, [Error::createLocatedError($e)])
@ -278,10 +300,13 @@ class Helper
} }
if ($config->getErrorFormatter() || $config->getDebug()) { if ($config->getErrorFormatter() || $config->getDebug()) {
$result->setErrorFormatter( $result->setErrorFormatter(
FormattedError::prepareFormatter($config->getErrorFormatter(), FormattedError::prepareFormatter(
$config->getDebug()) $config->getErrorFormatter(),
$config->getDebug()
)
); );
} }
return $result; return $result;
}; };
@ -289,25 +314,23 @@ class Helper
} }
/** /**
* @param ServerConfig $config
* @param OperationParams $op
* @return mixed * @return mixed
* @throws RequestError * @throws RequestError
*/ */
private function loadPersistedQuery(ServerConfig $config, OperationParams $op) private function loadPersistedQuery(ServerConfig $config, OperationParams $operationParams)
{ {
// Load query if we got persisted query id: // Load query if we got persisted query id:
$loader = $config->getPersistentQueryLoader(); $loader = $config->getPersistentQueryLoader();
if (!$loader) { if (! $loader) {
throw new RequestError("Persisted queries are not supported by this server"); throw new RequestError('Persisted queries are not supported by this server');
} }
$source = $loader($op->queryId, $op); $source = $loader($operationParams->queryId, $operationParams);
if (!is_string($source) && !$source instanceof DocumentNode) { if (! is_string($source) && ! $source instanceof DocumentNode) {
throw new InvariantViolation(sprintf( throw new InvariantViolation(sprintf(
"Persistent query loader must return query string or instance of %s but got: %s", 'Persistent query loader must return query string or instance of %s but got: %s',
DocumentNode::class, DocumentNode::class,
Utils::printSafe($source) Utils::printSafe($source)
)); ));
@ -317,23 +340,24 @@ class Helper
} }
/** /**
* @param ServerConfig $config * @param string $operationType
* @param OperationParams $params * @return mixed[]|null
* @param DocumentNode $doc
* @param $operationType
* @return array
*/ */
private function resolveValidationRules(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType) private function resolveValidationRules(
{ ServerConfig $config,
OperationParams $params,
DocumentNode $doc,
$operationType
) {
// Allow customizing validation rules per operation: // Allow customizing validation rules per operation:
$validationRules = $config->getValidationRules(); $validationRules = $config->getValidationRules();
if (is_callable($validationRules)) { if (is_callable($validationRules)) {
$validationRules = $validationRules($params, $doc, $operationType); $validationRules = $validationRules($params, $doc, $operationType);
if (!is_array($validationRules)) { if (! is_array($validationRules)) {
throw new InvariantViolation(sprintf( throw new InvariantViolation(sprintf(
"Expecting validation rules to be array or callable returning array, but got: %s", 'Expecting validation rules to be array or callable returning array, but got: %s',
Utils::printSafe($validationRules) Utils::printSafe($validationRules)
)); ));
} }
@ -343,10 +367,7 @@ class Helper
} }
/** /**
* @param ServerConfig $config * @param string $operationType
* @param OperationParams $params
* @param DocumentNode $doc
* @param $operationType
* @return mixed * @return mixed
*/ */
private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType) private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType)
@ -361,14 +382,15 @@ class Helper
} }
/** /**
* @param ServerConfig $config * @param string $operationType
* @param OperationParams $params
* @param DocumentNode $doc
* @param $operationType
* @return mixed * @return mixed
*/ */
private function resolveContextValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType) private function resolveContextValue(
{ ServerConfig $config,
OperationParams $params,
DocumentNode $doc,
$operationType
) {
$context = $config->getContext(); $context = $config->getContext();
if ($context instanceof \Closure) { if ($context instanceof \Closure) {
@ -383,12 +405,12 @@ class Helper
* *
* @api * @api
* @param Promise|ExecutionResult|ExecutionResult[] $result * @param Promise|ExecutionResult|ExecutionResult[] $result
* @param bool $exitWhenDone * @param bool $exitWhenDone
*/ */
public function sendResponse($result, $exitWhenDone = false) public function sendResponse($result, $exitWhenDone = false)
{ {
if ($result instanceof Promise) { if ($result instanceof Promise) {
$result->then(function($actualResult) use ($exitWhenDone) { $result->then(function ($actualResult) use ($exitWhenDone) {
$this->doSendResponse($actualResult, $exitWhenDone); $this->doSendResponse($actualResult, $exitWhenDone);
}); });
} else { } else {
@ -396,10 +418,6 @@ class Helper
} }
} }
/**
* @param $result
* @param $exitWhenDone
*/
private function doSendResponse($result, $exitWhenDone) private function doSendResponse($result, $exitWhenDone)
{ {
$httpStatus = $this->resolveHttpStatus($result); $httpStatus = $this->resolveHttpStatus($result);
@ -407,9 +425,9 @@ class Helper
} }
/** /**
* @param array|\JsonSerializable $jsonSerializable * @param mixed[]|\JsonSerializable $jsonSerializable
* @param int $httpStatus * @param int $httpStatus
* @param bool $exitWhenDone * @param bool $exitWhenDone
*/ */
public function emitResponse($jsonSerializable, $httpStatus, $exitWhenDone) public function emitResponse($jsonSerializable, $httpStatus, $exitWhenDone)
{ {
@ -431,37 +449,41 @@ class Helper
} }
/** /**
* @param $result * @param ExecutionResult|mixed[] $result
* @return int * @return int
*/ */
private function resolveHttpStatus($result) private function resolveHttpStatus($result)
{ {
if (is_array($result) && isset($result[0])) { if (is_array($result) && isset($result[0])) {
Utils::each($result, function ($executionResult, $index) { Utils::each(
if (!$executionResult instanceof ExecutionResult) { $result,
throw new InvariantViolation(sprintf( function ($executionResult, $index) {
"Expecting every entry of batched query result to be instance of %s but entry at position %d is %s", if (! $executionResult instanceof ExecutionResult) {
ExecutionResult::class, throw new InvariantViolation(sprintf(
$index, 'Expecting every entry of batched query result to be instance of %s but entry at position %d is %s',
Utils::printSafe($executionResult) ExecutionResult::class,
)); $index,
Utils::printSafe($executionResult)
));
}
} }
}); );
$httpStatus = 200; $httpStatus = 200;
} else { } else {
if (!$result instanceof ExecutionResult) { if (! $result instanceof ExecutionResult) {
throw new InvariantViolation(sprintf( throw new InvariantViolation(sprintf(
"Expecting query result to be instance of %s but got %s", 'Expecting query result to be instance of %s but got %s',
ExecutionResult::class, ExecutionResult::class,
Utils::printSafe($result) Utils::printSafe($result)
)); ));
} }
if ($result->data === null && !empty($result->errors)) { if ($result->data === null && ! empty($result->errors)) {
$httpStatus = 400; $httpStatus = 400;
} else { } else {
$httpStatus = 200; $httpStatus = 200;
} }
} }
return $httpStatus; return $httpStatus;
} }
@ -469,8 +491,7 @@ class Helper
* Converts PSR-7 request to OperationParams[] * Converts PSR-7 request to OperationParams[]
* *
* @api * @api
* @param ServerRequestInterface $request * @return OperationParams[]|OperationParams
* @return array|Helper
* @throws RequestError * @throws RequestError
*/ */
public function parsePsrRequest(ServerRequestInterface $request) public function parsePsrRequest(ServerRequestInterface $request)
@ -480,32 +501,32 @@ class Helper
} else { } else {
$contentType = $request->getHeader('content-type'); $contentType = $request->getHeader('content-type');
if (!isset($contentType[0])) { if (! isset($contentType[0])) {
throw new RequestError('Missing "Content-Type" header'); throw new RequestError('Missing "Content-Type" header');
} }
if (stripos($contentType[0], 'application/graphql') !== false) { if (stripos($contentType[0], 'application/graphql') !== false) {
$bodyParams = ['query' => $request->getBody()->getContents()]; $bodyParams = ['query' => $request->getBody()->getContents()];
} else if (stripos($contentType[0], 'application/json') !== false) { } elseif (stripos($contentType[0], 'application/json') !== false) {
$bodyParams = $request->getParsedBody(); $bodyParams = $request->getParsedBody();
if (null === $bodyParams) { if ($bodyParams === null) {
throw new InvariantViolation( throw new InvariantViolation(
"PSR-7 request is expected to provide parsed body for \"application/json\" requests but got null" 'PSR-7 request is expected to provide parsed body for "application/json" requests but got null'
); );
} }
if (!is_array($bodyParams)) { if (! is_array($bodyParams)) {
throw new RequestError( throw new RequestError(
"GraphQL Server expects JSON object or array, but got " . 'GraphQL Server expects JSON object or array, but got ' .
Utils::printSafeJson($bodyParams) Utils::printSafeJson($bodyParams)
); );
} }
} else { } else {
$bodyParams = $request->getParsedBody(); $bodyParams = $request->getParsedBody();
if (!is_array($bodyParams)) { if (! is_array($bodyParams)) {
throw new RequestError("Unexpected content type: " . Utils::printSafeJson($contentType[0])); throw new RequestError('Unexpected content type: ' . Utils::printSafeJson($contentType[0]));
} }
} }
} }
@ -522,19 +543,17 @@ class Helper
* *
* @api * @api
* @param Promise|ExecutionResult|ExecutionResult[] $result * @param Promise|ExecutionResult|ExecutionResult[] $result
* @param ResponseInterface $response
* @param StreamInterface $writableBodyStream
* @return Promise|ResponseInterface * @return Promise|ResponseInterface
*/ */
public function toPsrResponse($result, ResponseInterface $response, StreamInterface $writableBodyStream) public function toPsrResponse($result, ResponseInterface $response, StreamInterface $writableBodyStream)
{ {
if ($result instanceof Promise) { if ($result instanceof Promise) {
return $result->then(function($actualResult) use ($response, $writableBodyStream) { return $result->then(function ($actualResult) use ($response, $writableBodyStream) {
return $this->doConvertToPsrResponse($actualResult, $response, $writableBodyStream); return $this->doConvertToPsrResponse($actualResult, $response, $writableBodyStream);
}); });
} else {
return $this->doConvertToPsrResponse($result, $response, $writableBodyStream);
} }
return $this->doConvertToPsrResponse($result, $response, $writableBodyStream);
} }
private function doConvertToPsrResponse($result, ResponseInterface $response, StreamInterface $writableBodyStream) private function doConvertToPsrResponse($result, ResponseInterface $response, StreamInterface $writableBodyStream)

View File

@ -1,6 +1,15 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Server; namespace GraphQL\Server;
use const CASE_LOWER;
use function array_change_key_case;
use function is_string;
use function json_decode;
use function json_last_error;
/** /**
* Structure representing parsed HTTP parameters for GraphQL operation * Structure representing parsed HTTP parameters for GraphQL operation
*/ */
@ -33,33 +42,29 @@ class OperationParams
/** /**
* @api * @api
* @var array * @var mixed[]|null
*/ */
public $variables; public $variables;
/** /** @var mixed[] */
* @var array
*/
private $originalInput; private $originalInput;
/** /** @var bool */
* @var bool
*/
private $readOnly; private $readOnly;
/** /**
* Creates an instance from given array * Creates an instance from given array
* *
* @api * @api
* @param array $params * @param mixed[] $params
* @param bool $readonly * @param bool $readonly
* @return OperationParams * @return OperationParams
*/ */
public static function create(array $params, $readonly = false) public static function create(array $params, $readonly = false)
{ {
$instance = new static(); $instance = new static();
$params = array_change_key_case($params, CASE_LOWER); $params = array_change_key_case($params, CASE_LOWER);
$instance->originalInput = $params; $instance->originalInput = $params;
$params += [ $params += [
@ -68,25 +73,25 @@ class OperationParams
'documentid' => null, // alias to queryid 'documentid' => null, // alias to queryid
'id' => null, // alias to queryid 'id' => null, // alias to queryid
'operationname' => null, 'operationname' => null,
'variables' => null 'variables' => null,
]; ];
if ($params['variables'] === "") { if ($params['variables'] === '') {
$params['variables'] = null; $params['variables'] = null;
} }
if (is_string($params['variables'])) { if (is_string($params['variables'])) {
$tmp = json_decode($params['variables'], true); $tmp = json_decode($params['variables'], true);
if (!json_last_error()) { if (! json_last_error()) {
$params['variables'] = $tmp; $params['variables'] = $tmp;
} }
} }
$instance->query = $params['query']; $instance->query = $params['query'];
$instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id']; $instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id'];
$instance->operation = $params['operationname']; $instance->operation = $params['operationname'];
$instance->variables = $params['variables']; $instance->variables = $params['variables'];
$instance->readOnly = (bool) $readonly; $instance->readOnly = (bool) $readonly;
return $instance; return $instance;
} }
@ -98,7 +103,7 @@ class OperationParams
*/ */
public function getOriginalInput($key) public function getOriginalInput($key)
{ {
return isset($this->originalInput[$key]) ? $this->originalInput[$key] : null; return $this->originalInput[$key] ?? null;
} }
/** /**

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Server; namespace GraphQL\Server;
use GraphQL\Error\ClientAware; use GraphQL\Error\ClientAware;

View File

@ -1,10 +1,19 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Server; namespace GraphQL\Server;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\PromiseAdapter; use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use GraphQL\Validator\Rules\AbstractValidationRule;
use function is_array;
use function is_callable;
use function method_exists;
use function sprintf;
use function ucfirst;
/** /**
* Server configuration class. * Server configuration class.
@ -26,7 +35,7 @@ class ServerConfig
* (or just returns empty config when array is not passed). * (or just returns empty config when array is not passed).
* *
* @api * @api
* @param array $config * @param mixed[] $config
* @return ServerConfig * @return ServerConfig
*/ */
public static function create(array $config = []) public static function create(array $config = [])
@ -34,99 +43,80 @@ class ServerConfig
$instance = new static(); $instance = new static();
foreach ($config as $key => $value) { foreach ($config as $key => $value) {
$method = 'set' . ucfirst($key); $method = 'set' . ucfirst($key);
if (!method_exists($instance, $method)) { if (! method_exists($instance, $method)) {
throw new InvariantViolation("Unknown server config option \"$key\""); throw new InvariantViolation(sprintf('Unknown server config option "%s"', $key));
} }
$instance->$method($value); $instance->$method($value);
} }
return $instance; return $instance;
} }
/** /** @var Schema */
* @var Schema
*/
private $schema; private $schema;
/** /** @var mixed|\Closure */
* @var mixed|\Closure
*/
private $context; private $context;
/** /** @var mixed|\Closure */
* @var mixed|\Closure
*/
private $rootValue; private $rootValue;
/** /** @var callable|null */
* @var callable|null
*/
private $errorFormatter; private $errorFormatter;
/** /** @var callable|null */
* @var callable|null
*/
private $errorsHandler; private $errorsHandler;
/** /** @var bool */
* @var bool
*/
private $debug = false; private $debug = false;
/** /** @var bool */
* @var bool
*/
private $queryBatching = false; private $queryBatching = false;
/** /** @var AbstractValidationRule[]|callable */
* @var array|callable
*/
private $validationRules; private $validationRules;
/** /** @var callable */
* @var callable
*/
private $fieldResolver; private $fieldResolver;
/** /** @var PromiseAdapter */
* @var PromiseAdapter
*/
private $promiseAdapter; private $promiseAdapter;
/** /** @var callable */
* @var callable
*/
private $persistentQueryLoader; private $persistentQueryLoader;
/** /**
* @api * @api
* @param Schema $schema * @return self
* @return $this
*/ */
public function setSchema(Schema $schema) public function setSchema(Schema $schema)
{ {
$this->schema = $schema; $this->schema = $schema;
return $this; return $this;
} }
/** /**
* @api * @api
* @param mixed|\Closure $context * @param mixed|\Closure $context
* @return $this * @return self
*/ */
public function setContext($context) public function setContext($context)
{ {
$this->context = $context; $this->context = $context;
return $this; return $this;
} }
/** /**
* @api * @api
* @param mixed|\Closure $rootValue * @param mixed|\Closure $rootValue
* @return $this * @return self
*/ */
public function setRootValue($rootValue) public function setRootValue($rootValue)
{ {
$this->rootValue = $rootValue; $this->rootValue = $rootValue;
return $this; return $this;
} }
@ -134,12 +124,12 @@ class ServerConfig
* Expects function(Throwable $e) : array * Expects function(Throwable $e) : array
* *
* @api * @api
* @param callable $errorFormatter * @return self
* @return $this
*/ */
public function setErrorFormatter(callable $errorFormatter) public function setErrorFormatter(callable $errorFormatter)
{ {
$this->errorFormatter = $errorFormatter; $this->errorFormatter = $errorFormatter;
return $this; return $this;
} }
@ -147,12 +137,12 @@ class ServerConfig
* Expects function(array $errors, callable $formatter) : array * Expects function(array $errors, callable $formatter) : array
* *
* @api * @api
* @param callable $handler * @return self
* @return $this
*/ */
public function setErrorsHandler(callable $handler) public function setErrorsHandler(callable $handler)
{ {
$this->errorsHandler = $handler; $this->errorsHandler = $handler;
return $this; return $this;
} }
@ -160,12 +150,12 @@ class ServerConfig
* Set validation rules for this server. * Set validation rules for this server.
* *
* @api * @api
* @param array|callable * @param AbstractValidationRule[]|callable $validationRules
* @return $this * @return self
*/ */
public function setValidationRules($validationRules) public function setValidationRules($validationRules)
{ {
if (!is_callable($validationRules) && !is_array($validationRules) && $validationRules !== null) { if (! is_callable($validationRules) && ! is_array($validationRules) && $validationRules !== null) {
throw new InvariantViolation( throw new InvariantViolation(
'Server config expects array of validation rules or callable returning such array, but got ' . 'Server config expects array of validation rules or callable returning such array, but got ' .
Utils::printSafe($validationRules) Utils::printSafe($validationRules)
@ -173,17 +163,18 @@ class ServerConfig
} }
$this->validationRules = $validationRules; $this->validationRules = $validationRules;
return $this; return $this;
} }
/** /**
* @api * @api
* @param callable $fieldResolver * @return self
* @return $this
*/ */
public function setFieldResolver(callable $fieldResolver) public function setFieldResolver(callable $fieldResolver)
{ {
$this->fieldResolver = $fieldResolver; $this->fieldResolver = $fieldResolver;
return $this; return $this;
} }
@ -193,12 +184,12 @@ class ServerConfig
* This function must return query string or valid DocumentNode. * This function must return query string or valid DocumentNode.
* *
* @api * @api
* @param callable $persistentQueryLoader * @return self
* @return $this
*/ */
public function setPersistentQueryLoader(callable $persistentQueryLoader) public function setPersistentQueryLoader(callable $persistentQueryLoader)
{ {
$this->persistentQueryLoader = $persistentQueryLoader; $this->persistentQueryLoader = $persistentQueryLoader;
return $this; return $this;
} }
@ -207,11 +198,12 @@ class ServerConfig
* *
* @api * @api
* @param bool|int $set * @param bool|int $set
* @return $this * @return self
*/ */
public function setDebug($set = true) public function setDebug($set = true)
{ {
$this->debug = $set; $this->debug = $set;
return $this; return $this;
} }
@ -220,22 +212,23 @@ class ServerConfig
* *
* @api * @api
* @param bool $enableBatching * @param bool $enableBatching
* @return $this * @return self
*/ */
public function setQueryBatching($enableBatching) public function setQueryBatching($enableBatching)
{ {
$this->queryBatching = (bool) $enableBatching; $this->queryBatching = (bool) $enableBatching;
return $this; return $this;
} }
/** /**
* @api * @api
* @param PromiseAdapter $promiseAdapter * @return self
* @return $this
*/ */
public function setPromiseAdapter(PromiseAdapter $promiseAdapter) public function setPromiseAdapter(PromiseAdapter $promiseAdapter)
{ {
$this->promiseAdapter = $promiseAdapter; $this->promiseAdapter = $promiseAdapter;
return $this; return $this;
} }
@ -288,7 +281,7 @@ class ServerConfig
} }
/** /**
* @return array|callable * @return AbstractValidationRule[]|callable
*/ */
public function getValidationRules() public function getValidationRules()
{ {

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Server; namespace GraphQL\Server;
use GraphQL\Error\FormattedError; use GraphQL\Error\FormattedError;
@ -9,6 +12,7 @@ use GraphQL\Utils;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use function is_array;
/** /**
* GraphQL server compatible with both: [express-graphql](https://github.com/graphql/express-graphql) * GraphQL server compatible with both: [express-graphql](https://github.com/graphql/express-graphql)
@ -34,14 +38,10 @@ use Psr\Http\Message\StreamInterface;
*/ */
class StandardServer class StandardServer
{ {
/** /** @var ServerConfig */
* @var ServerConfig
*/
private $config; private $config;
/** /** @var Helper */
* @var Helper
*/
private $helper; private $helper;
/** /**
@ -51,17 +51,15 @@ class StandardServer
* *
* @api * @api
* @param \Throwable $error * @param \Throwable $error
* @param bool $debug * @param bool $debug
* @param bool $exitWhenDone * @param bool $exitWhenDone
*/ */
public static function send500Error($error, $debug = false, $exitWhenDone = false) public static function send500Error($error, $debug = false, $exitWhenDone = false)
{ {
$response = [ $response = [
'errors' => [ 'errors' => [FormattedError::createFromException($error, $debug)],
FormattedError::createFromException($error, $debug)
]
]; ];
$helper = new Helper(); $helper = new Helper();
$helper->emitResponse($response, 500, $exitWhenDone); $helper->emitResponse($response, 500, $exitWhenDone);
} }
@ -69,15 +67,15 @@ class StandardServer
* Creates new instance of a standard GraphQL HTTP server * Creates new instance of a standard GraphQL HTTP server
* *
* @api * @api
* @param ServerConfig|array $config * @param ServerConfig|mixed[] $config
*/ */
public function __construct($config) public function __construct($config)
{ {
if (is_array($config)) { if (is_array($config)) {
$config = ServerConfig::create($config); $config = ServerConfig::create($config);
} }
if (!$config instanceof ServerConfig) { if (! $config instanceof ServerConfig) {
throw new InvariantViolation("Expecting valid server config, but got " . Utils::printSafe($config)); throw new InvariantViolation('Expecting valid server config, but got ' . Utils::printSafe($config));
} }
$this->config = $config; $this->config = $config;
$this->helper = new Helper(); $this->helper = new Helper();
@ -95,7 +93,7 @@ class StandardServer
* *
* @api * @api
* @param OperationParams|OperationParams[] $parsedBody * @param OperationParams|OperationParams[] $parsedBody
* @param bool $exitWhenDone * @param bool $exitWhenDone
*/ */
public function handleRequest($parsedBody = null, $exitWhenDone = false) public function handleRequest($parsedBody = null, $exitWhenDone = false)
{ {
@ -120,15 +118,15 @@ class StandardServer
*/ */
public function executeRequest($parsedBody = null) public function executeRequest($parsedBody = null)
{ {
if (null === $parsedBody) { if ($parsedBody === null) {
$parsedBody = $this->helper->parseHttpRequest(); $parsedBody = $this->helper->parseHttpRequest();
} }
if (is_array($parsedBody)) { if (is_array($parsedBody)) {
return $this->helper->executeBatch($this->config, $parsedBody); return $this->helper->executeBatch($this->config, $parsedBody);
} else {
return $this->helper->executeOperation($this->config, $parsedBody);
} }
return $this->helper->executeOperation($this->config, $parsedBody);
} }
/** /**
@ -138,17 +136,13 @@ class StandardServer
* (e.g. using specific JsonResponse instance of some framework). * (e.g. using specific JsonResponse instance of some framework).
* *
* @api * @api
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param StreamInterface $writableBodyStream
* @return ResponseInterface|Promise * @return ResponseInterface|Promise
*/ */
public function processPsrRequest( public function processPsrRequest(
ServerRequestInterface $request, ServerRequestInterface $request,
ResponseInterface $response, ResponseInterface $response,
StreamInterface $writableBodyStream StreamInterface $writableBodyStream
) ) {
{
$result = $this->executePsrRequest($request); $result = $this->executePsrRequest($request);
return $this->helper->toPsrResponse($result, $response, $writableBodyStream); return $this->helper->toPsrResponse($result, $response, $writableBodyStream);
} }
@ -158,7 +152,6 @@ class StandardServer
* (or promise when promise adapter is different from SyncPromiseAdapter) * (or promise when promise adapter is different from SyncPromiseAdapter)
* *
* @api * @api
* @param ServerRequestInterface $request
* @return ExecutionResult|ExecutionResult[]|Promise * @return ExecutionResult|ExecutionResult[]|Promise
*/ */
public function executePsrRequest(ServerRequestInterface $request) public function executePsrRequest(ServerRequestInterface $request)