22namespace GraphQL ;
33
44use GraphQL \Error \Error ;
5+ use GraphQL \Error \InvariantViolation ;
56use GraphQL \Executor \ExecutionResult ;
67use GraphQL \Executor \Promise \PromiseAdapter ;
78use GraphQL \Language \AST \DocumentNode ;
89use GraphQL \Language \Parser ;
9- use GraphQL \Type \Definition \Config ;
1010use GraphQL \Type \Definition \Directive ;
1111use GraphQL \Type \Definition \ObjectType ;
1212use 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