Skip to content

Commit 794d367

Browse files
committed
Initial pass on standard server implementation (also deprecated current GraphQL\Server which is undocumented anyway)
1 parent a3b40db commit 794d367

File tree

11 files changed

+1312
-616
lines changed

11 files changed

+1312
-616
lines changed

src/Error/FormattedError.php

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,49 @@
88

99
/**
1010
* Class FormattedError
11-
* @todo move this class to Utils/ErrorUtils
11+
*
1212
* @package GraphQL\Error
1313
*/
1414
class FormattedError
1515
{
1616
/**
17-
* @deprecated as of 8.0
18-
* @param $error
19-
* @param SourceLocation[] $locations
17+
* @param \Throwable $e
18+
* @param $debug
19+
*
2020
* @return array
2121
*/
22-
public static function create($error, array $locations = [])
22+
public static function createFromException($e, $debug = false)
2323
{
24-
$formatted = [
25-
'message' => $error
26-
];
27-
28-
if (!empty($locations)) {
29-
$formatted['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations);
24+
if ($e instanceof Error) {
25+
$result = $e->toSerializableArray();
26+
} else if ($e instanceof \ErrorException) {
27+
$result = [
28+
'message' => $e->getMessage(),
29+
];
30+
if ($debug) {
31+
$result += [
32+
'file' => $e->getFile(),
33+
'line' => $e->getLine(),
34+
'severity' => $e->getSeverity()
35+
];
36+
}
37+
} else {
38+
Utils::invariant(
39+
$e instanceof \Exception || $e instanceof \Throwable,
40+
"Expected exception, got %s",
41+
Utils::getVariableType($e)
42+
);
43+
$result = [
44+
'message' => $e->getMessage()
45+
];
3046
}
3147

32-
return $formatted;
33-
}
34-
35-
/**
36-
* @param \ErrorException $e
37-
* @return array
38-
*/
39-
public static function createFromPHPError(\ErrorException $e)
40-
{
41-
return [
42-
'message' => $e->getMessage(),
43-
'severity' => $e->getSeverity(),
44-
'trace' => self::toSafeTrace($e->getTrace())
45-
];
46-
}
48+
if ($debug) {
49+
$debugging = $e->getPrevious() ?: $e;
50+
$result['trace'] = static::toSafeTrace($debugging->getTrace());
51+
}
4752

48-
/**
49-
* @param \Throwable $e
50-
* @return array
51-
*/
52-
public static function createFromException($e)
53-
{
54-
return [
55-
'message' => $e->getMessage(),
56-
'trace' => self::toSafeTrace($e->getTrace())
57-
];
53+
return $result;
5854
}
5955

6056
/**
@@ -133,4 +129,37 @@ public static function printVar($var)
133129
}
134130
return gettype($var);
135131
}
132+
133+
/**
134+
* @deprecated as of v0.8.0
135+
* @param $error
136+
* @param SourceLocation[] $locations
137+
* @return array
138+
*/
139+
public static function create($error, array $locations = [])
140+
{
141+
$formatted = [
142+
'message' => $error
143+
];
144+
145+
if (!empty($locations)) {
146+
$formatted['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations);
147+
}
148+
149+
return $formatted;
150+
}
151+
152+
/**
153+
* @param \ErrorException $e
154+
* @deprecated as of v0.10.0, use general purpose method createFromException() instead
155+
* @return array
156+
*/
157+
public static function createFromPHPError(\ErrorException $e)
158+
{
159+
return [
160+
'message' => $e->getMessage(),
161+
'severity' => $e->getSeverity(),
162+
'trace' => self::toSafeTrace($e->getTrace())
163+
];
164+
}
136165
}

src/Server.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
use GraphQL\Validator\DocumentValidator;
1616
use GraphQL\Utils\Utils;
1717

18+
trigger_error(
19+
'GraphQL\Server is deprecated in favor of new implementation: GraphQL\Server\StandardServer and will be removed in next version',
20+
E_USER_DEPRECATED
21+
);
22+
1823
class Server
1924
{
2025
const DEBUG_PHP_ERRORS = 1;

src/Server/Helper.php

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php
2+
namespace GraphQL\Server;
3+
4+
use GraphQL\Error\FormattedError;
5+
use GraphQL\Error\InvariantViolation;
6+
use GraphQL\Error\UserError;
7+
use GraphQL\Executor\ExecutionResult;
8+
use GraphQL\Executor\Promise\Promise;
9+
use GraphQL\GraphQL;
10+
use GraphQL\Language\AST\DocumentNode;
11+
use GraphQL\Language\Parser;
12+
use GraphQL\Utils\AST;
13+
use GraphQL\Utils\Utils;
14+
15+
/**
16+
* Class Helper
17+
* Contains functionality that could be re-used by various server implementations
18+
*
19+
* @package GraphQL\Server
20+
*/
21+
class Helper
22+
{
23+
/**
24+
* Executes GraphQL operation with given server configuration and returns execution result (or promise)
25+
*
26+
* @param ServerConfig $config
27+
* @param OperationParams $op
28+
*
29+
* @return ExecutionResult|Promise
30+
*/
31+
public static function executeOperation(ServerConfig $config, OperationParams $op)
32+
{
33+
$phpErrors = [];
34+
$execute = function() use ($config, $op) {
35+
$doc = $op->queryId ? static::loadPersistedQuery($config, $op) : $op->query;
36+
37+
if (!$doc instanceof DocumentNode) {
38+
$doc = Parser::parse($doc);
39+
}
40+
if (!$op->allowsMutation() && AST::isMutation($op->operation, $doc)) {
41+
throw new UserError("Cannot execute mutation in read-only context");
42+
}
43+
44+
return GraphQL::executeAndReturnResult(
45+
$config->getSchema(),
46+
$doc,
47+
$config->getRootValue(),
48+
$config->getContext(),
49+
$op->variables,
50+
$op->operation,
51+
$config->getDefaultFieldResolver(),
52+
static::resolveValidationRules($config, $op),
53+
$config->getPromiseAdapter()
54+
);
55+
};
56+
if ($config->getDebug()) {
57+
$execute = Utils::withErrorHandling($execute, $phpErrors);
58+
}
59+
$result = $execute();
60+
61+
$applyErrorFormatting = function(ExecutionResult $result) use ($config, $phpErrors) {
62+
if ($config->getDebug()) {
63+
$errorFormatter = function($e) {
64+
return FormattedError::createFromException($e, true);
65+
};
66+
} else {
67+
$errorFormatter = $config->getErrorFormatter();
68+
}
69+
if (!empty($phpErrors)) {
70+
$result->extensions['phpErrors'] = array_map($errorFormatter, $phpErrors);
71+
}
72+
$result->setErrorFormatter($errorFormatter);
73+
return $result;
74+
};
75+
76+
return $result instanceof Promise ?
77+
$result->then($applyErrorFormatting) :
78+
$applyErrorFormatting($result);
79+
}
80+
81+
/**
82+
* @param ServerConfig $config
83+
* @param OperationParams $op
84+
* @return string|DocumentNode
85+
*/
86+
private static function loadPersistedQuery(ServerConfig $config, OperationParams $op)
87+
{
88+
// Load query if we got persisted query id:
89+
$loader = $config->getPersistentQueryLoader();
90+
91+
if (!$loader) {
92+
throw new UserError("Persisted queries are not supported by this server");
93+
}
94+
95+
$source = $loader($op->queryId, $op);
96+
97+
if (!is_string($source) && !$source instanceof DocumentNode) {
98+
throw new InvariantViolation(sprintf(
99+
"Persistent query loader must return query string or instance of %s but got: %s",
100+
DocumentNode::class,
101+
Utils::printSafe($source)
102+
));
103+
}
104+
105+
return $source;
106+
}
107+
108+
/**
109+
* @param ServerConfig $config
110+
* @param OperationParams $params
111+
* @return array
112+
*/
113+
private static function resolveValidationRules(ServerConfig $config, OperationParams $params)
114+
{
115+
// Allow customizing validation rules per operation:
116+
$validationRules = $config->getValidationRules();
117+
118+
if (is_callable($validationRules)) {
119+
$validationRules = $validationRules($params);
120+
121+
if (!is_array($validationRules)) {
122+
throw new InvariantViolation(
123+
"Validation rules callable must return array of rules, but got: %s" .
124+
Utils::printSafe($validationRules)
125+
);
126+
}
127+
}
128+
129+
return $validationRules;
130+
}
131+
132+
/**
133+
* Parses HTTP request and returns GraphQL QueryParams contained in this request.
134+
* For batched requests it returns an array of QueryParams.
135+
*
136+
* @return OperationParams|OperationParams[]
137+
*/
138+
public static function parseHttpRequest()
139+
{
140+
$contentType = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : null;
141+
142+
$assertValid = function (OperationParams $opParams, $queryNum = null) {
143+
$errors = $opParams->validate();
144+
if (!empty($errors[0])) {
145+
$err = $queryNum ? "Error in query #$queryNum: {$errors[0]}" : $errors[0];
146+
throw new UserError($err);
147+
}
148+
};
149+
150+
if (stripos($contentType, 'application/graphql' !== false)) {
151+
$body = file_get_contents('php://input') ?: '';
152+
$op = OperationParams::create(['query' => $body]);
153+
$assertValid($op);
154+
} else if (stripos($contentType, 'application/json') !== false || stripos($contentType, 'text/json') !== false) {
155+
$body = file_get_contents('php://input') ?: '';
156+
$data = json_decode($body, true);
157+
158+
if (json_last_error()) {
159+
throw new UserError("Could not parse JSON: " . json_last_error_msg());
160+
}
161+
if (!is_array($data)) {
162+
throw new UserError(
163+
"GraphQL Server expects JSON object or array, but got %s" .
164+
Utils::printSafe($data)
165+
);
166+
}
167+
if (isset($data[0])) {
168+
$op = [];
169+
foreach ($data as $index => $entry) {
170+
$params = OperationParams::create($entry);
171+
$assertValid($params, $index);
172+
$op[] = $params;
173+
}
174+
} else {
175+
$op = OperationParams::create($data);
176+
$assertValid($op);
177+
}
178+
} else if (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
179+
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
180+
$op = OperationParams::create($_GET, false);
181+
} else {
182+
$op = OperationParams::create($_POST);
183+
}
184+
$assertValid($op);
185+
} else {
186+
throw new UserError("Bad request: unexpected content type: " . Utils::printSafe($contentType));
187+
}
188+
189+
return $op;
190+
}
191+
}

0 commit comments

Comments
 (0)