Skip to content

Commit e287595

Browse files
committed
Tests for new GraphQL\Server facade
1 parent 8e75cc3 commit e287595

File tree

2 files changed

+653
-25
lines changed

2 files changed

+653
-25
lines changed

src/Server.php

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
namespace GraphQL;
33

44
use GraphQL\Error\Error;
5+
use GraphQL\Error\InvariantViolation;
56
use GraphQL\Executor\ExecutionResult;
67
use GraphQL\Executor\Promise\PromiseAdapter;
78
use GraphQL\Language\AST\DocumentNode;
89
use GraphQL\Language\Parser;
9-
use GraphQL\Type\Definition\Config;
1010
use GraphQL\Type\Definition\Directive;
1111
use GraphQL\Type\Definition\ObjectType;
1212
use GraphQL\Type\Definition\Type;
@@ -17,7 +17,6 @@ class Server
1717
{
1818
const DEBUG_PHP_ERRORS = 1;
1919
const DEBUG_EXCEPTIONS = 2;
20-
const DEBUG_SCHEMA_CONFIGS = 4;
2120
const DEBUG_ALL = 7;
2221

2322
private $queryType;
@@ -88,6 +87,7 @@ public function getQueryType()
8887
*/
8988
public function setQueryType(ObjectType $queryType)
9089
{
90+
$this->assertSchemaNotSet('Query Type', __METHOD__);
9191
$this->queryType = $queryType;
9292
return $this;
9393
}
@@ -106,6 +106,7 @@ public function getMutationType()
106106
*/
107107
public function setMutationType(ObjectType $mutationType)
108108
{
109+
$this->assertSchemaNotSet('Mutation Type', __METHOD__);
109110
$this->mutationType = $mutationType;
110111
return $this;
111112
}
@@ -124,6 +125,7 @@ public function getSubscriptionType()
124125
*/
125126
public function setSubscriptionType($subscriptionType)
126127
{
128+
$this->assertSchemaNotSet('Subscription Type', __METHOD__);
127129
$this->subscriptionType = $subscriptionType;
128130
return $this;
129131
}
@@ -134,10 +136,16 @@ public function setSubscriptionType($subscriptionType)
134136
*/
135137
public function addTypes(array $types)
136138
{
137-
$this->types = array_merge($this->types, $types);
139+
if (!empty($types)) {
140+
$this->assertSchemaNotSet('Types', __METHOD__);
141+
$this->types = array_merge($this->types, $types);
142+
}
138143
return $this;
139144
}
140145

146+
/**
147+
* @return array
148+
*/
141149
public function getTypes()
142150
{
143151
return $this->types;
@@ -161,6 +169,7 @@ public function getDirectives()
161169
*/
162170
public function setDirectives(array $directives)
163171
{
172+
$this->assertSchemaNotSet('Directives', __METHOD__);
164173
$this->directives = $directives;
165174
return $this;
166175
}
@@ -219,6 +228,58 @@ public function getRootValue()
219228
return $this->rootValue;
220229
}
221230

231+
/**
232+
* Set schema instance manually. Mutually exclusive with `setQueryType`, `setMutationType`, `setSubscriptionType`, `setDirectives`, `addTypes`
233+
*
234+
* @param Schema $schema
235+
* @return $this
236+
*/
237+
public function setSchema(Schema $schema)
238+
{
239+
if ($this->queryType) {
240+
$err = 'Query Type is already set';
241+
$errMethod = __CLASS__ . '::setQueryType';
242+
} else if ($this->mutationType) {
243+
$err = 'Mutation Type is already set';
244+
$errMethod = __CLASS__ . '::setMutationType';
245+
} else if ($this->subscriptionType) {
246+
$err = 'Subscription Type is already set';
247+
$errMethod = __CLASS__ . '::setSubscriptionType';
248+
} else if ($this->directives) {
249+
$err = 'Directives are already set';
250+
$errMethod = __CLASS__ . '::setDirectives';
251+
} else if ($this->types) {
252+
$err = 'Additional types are already set';
253+
$errMethod = __CLASS__ . '::addTypes';
254+
} else if ($this->typeResolutionStrategy) {
255+
$err = 'Type Resolution Strategy is already set';
256+
$errMethod = __CLASS__ . '::setTypeResolutionStrategy';
257+
} else if ($this->schema && $this->schema !== $schema) {
258+
$err = 'Different schema is already set';
259+
}
260+
261+
if (isset($err)) {
262+
if (isset($errMethod)) {
263+
$err .= " ($errMethod is mutually exclusive with " . __METHOD__ . ")";
264+
}
265+
throw new InvariantViolation("Cannot set Schema on Server: $err");
266+
}
267+
$this->schema = $schema;
268+
return $this;
269+
}
270+
271+
/**
272+
* @param $entry
273+
* @param $mutuallyExclusiveMethod
274+
*/
275+
private function assertSchemaNotSet($entry, $mutuallyExclusiveMethod)
276+
{
277+
if ($this->schema) {
278+
$schemaMethod = __CLASS__ . '::setSchema';
279+
throw new InvariantViolation("Cannot set $entry on Server: Schema is already set ($mutuallyExclusiveMethod is mutually exclusive with $schemaMethod)");
280+
}
281+
}
282+
222283
/**
223284
* @return Schema
224285
*/
@@ -246,6 +307,8 @@ public function getPhpErrorFormatter()
246307
}
247308

248309
/**
310+
* Expects function(\ErrorException $e) : array
311+
*
249312
* @param callable $phpErrorFormatter
250313
*/
251314
public function setPhpErrorFormatter(callable $phpErrorFormatter)
@@ -262,6 +325,8 @@ public function getExceptionFormatter()
262325
}
263326

264327
/**
328+
* Expects function(Exception $e) : array
329+
*
265330
* @param callable $exceptionFormatter
266331
*/
267332
public function setExceptionFormatter(callable $exceptionFormatter)
@@ -302,8 +367,11 @@ public function setUnexpectedErrorStatus($unexpectedErrorStatus)
302367
}
303368

304369
/**
370+
* Parses GraphQL query string and returns Abstract Syntax Tree for this query
371+
*
305372
* @param string $query
306373
* @return Language\AST\DocumentNode
374+
* @throws \GraphQL\Error\SyntaxError
307375
*/
308376
public function parse($query)
309377
{
@@ -323,10 +391,12 @@ public function getValidationRules()
323391

324392
/**
325393
* @param array $validationRules
394+
* @return $this
326395
*/
327396
public function setValidationRules(array $validationRules)
328397
{
329398
$this->validationRules = $validationRules;
399+
return $this;
330400
}
331401

332402
/**
@@ -343,12 +413,13 @@ public function getTypeResolutionStrategy()
343413
*/
344414
public function setTypeResolutionStrategy(Resolution $typeResolutionStrategy)
345415
{
416+
$this->assertSchemaNotSet('Type Resolution Strategy', __METHOD__);
346417
$this->typeResolutionStrategy = $typeResolutionStrategy;
347418
return $this;
348419
}
349420

350421
/**
351-
* @return PromiseAdapter
422+
* @return PromiseAdapter|null
352423
*/
353424
public function getPromiseAdapter()
354425
{
@@ -363,22 +434,33 @@ public function getPromiseAdapter()
363434
*/
364435
public function setPromiseAdapter(PromiseAdapter $promiseAdapter)
365436
{
437+
if ($this->promiseAdapter && $promiseAdapter !== $this->promiseAdapter) {
438+
throw new InvariantViolation("Cannot set promise adapter: Different adapter is already set");
439+
}
366440
$this->promiseAdapter = $promiseAdapter;
367441
return $this;
368442
}
369443

370444
/**
371-
* Returns array with validation errors
445+
* Returns array with validation errors (empty when there are no validation errors)
372446
*
373447
* @param DocumentNode $query
374448
* @return array
375449
*/
376450
public function validate(DocumentNode $query)
377451
{
378-
return DocumentValidator::validate($this->getSchema(), $query, $this->validationRules);
452+
try {
453+
$schema = $this->getSchema();
454+
} catch (InvariantViolation $e) {
455+
throw new InvariantViolation("Cannot validate, schema contains errors: {$e->getMessage()}", null, $e);
456+
}
457+
458+
return DocumentValidator::validate($schema, $query, $this->validationRules);
379459
}
380460

381461
/**
462+
* Executes GraphQL query against this server and returns execution result
463+
*
382464
* @param string|DocumentNode $query
383465
* @param array|null $variables
384466
* @param string|null $operationName
@@ -389,17 +471,10 @@ public function executeQuery($query, array $variables = null, $operationName = n
389471
$this->phpErrors = [];
390472
if ($this->debug & static::DEBUG_PHP_ERRORS) {
391473
// Catch custom errors (to report them in query results)
392-
$lastDisplayErrors = ini_get('display_errors');
393-
ini_set('display_errors', 0);
394-
395474
set_error_handler(function($severity, $message, $file, $line) {
396475
$this->phpErrors[] = new \ErrorException($message, 0, $severity, $file, $line);
397476
});
398477
}
399-
if ($this->debug & static::DEBUG_SCHEMA_CONFIGS) {
400-
$isConfigValidationEnabled = Config::isValidationEnabled();
401-
Config::enableValidation();
402-
}
403478

404479
$result = GraphQL::executeAndReturnResult(
405480
$this->getSchema(),
@@ -420,38 +495,40 @@ public function executeQuery($query, array $variables = null, $operationName = n
420495
$result->extensions['phpErrors'] = array_map($this->phpErrorFormatter, $this->phpErrors);
421496
}
422497

423-
if (isset($lastDisplayErrors)) {
424-
ini_set('display_errors', $lastDisplayErrors);
498+
if ($this->debug & static::DEBUG_PHP_ERRORS) {
425499
restore_error_handler();
426500
}
427-
if (isset($isConfigValidationEnabled) && !$isConfigValidationEnabled) {
428-
Config::disableValidation();
429-
}
430501
return $result;
431502
}
432503

504+
/**
505+
* GraphQL HTTP endpoint compatible with express-graphql
506+
*/
433507
public function handleRequest()
434508
{
435509
try {
436510
$httpStatus = 200;
437511
if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) {
438-
$raw = file_get_contents('php://input') ?: '';
512+
$raw = $this->readInput();
439513
$data = json_decode($raw, true);
440514
} else {
441515
$data = $_REQUEST;
442516
}
443517
$data += ['query' => null, 'variables' => null];
444518
$result = $this->executeQuery($data['query'], (array) $data['variables'])->toArray();
445-
} catch (\Exception $exception) {
519+
} catch (\Exception $e) {
446520
// This is only possible for schema creation errors and some very unpredictable errors,
447521
// (all errors which occur during query execution are caught and included in final response)
448522
$httpStatus = $this->unexpectedErrorStatus;
449-
$error = new Error($this->unexpectedErrorMessage, null, null, null, null, $exception);
523+
$error = new Error($this->unexpectedErrorMessage, null, null, null, null, $e);
524+
$result = ['errors' => [$this->formatError($error)]];
525+
} catch (\Error $e) {
526+
$httpStatus = $this->unexpectedErrorStatus;
527+
$error = new Error($this->unexpectedErrorMessage, null, null, null, null, $e);
450528
$result = ['errors' => [$this->formatError($error)]];
451529
}
452530

453-
header('Content-Type: application/json', true, $httpStatus);
454-
echo json_encode($result);
531+
$this->produceOutput($result, $httpStatus);
455532
}
456533

457534
private function formatException(\Exception $e)
@@ -473,4 +550,15 @@ public function formatError(\GraphQL\Error\Error $e)
473550
}
474551
return $result;
475552
}
553+
554+
protected function readInput()
555+
{
556+
return file_get_contents('php://input') ?: '';
557+
}
558+
559+
protected function produceOutput(array $result, $httpStatus)
560+
{
561+
header('Content-Type: application/json', true, $httpStatus);
562+
echo json_encode($result);
563+
}
476564
}

0 commit comments

Comments
 (0)