10
10
use GraphQL \Language \AST \NodeKind ;
11
11
use GraphQL \Language \AST \OperationDefinitionNode ;
12
12
use GraphQL \Language \AST \SelectionSetNode ;
13
+ use GraphQL \Promise \PromiseInterface ;
13
14
use GraphQL \Schema ;
14
15
use GraphQL \Type \Definition \AbstractType ;
15
16
use GraphQL \Type \Definition \Directive ;
@@ -169,10 +170,13 @@ private static function executeOperation(ExecutionContext $exeContext, Operation
169
170
170
171
$ path = [];
171
172
if ($ operation ->operation === 'mutation ' ) {
172
- return self ::executeFieldsSerially ($ exeContext , $ type , $ rootValue , $ path , $ fields );
173
+ $ results = self ::executeFieldsSerially ($ exeContext , $ type , $ rootValue , $ path , $ fields );
174
+ } else {
175
+ $ results = self ::executeFields ($ exeContext , $ type , $ rootValue , $ path , $ fields );
173
176
}
177
+ $ finalResults = self ::completePromiseIfNeeded ($ results );
174
178
175
- return self :: executeFields ( $ exeContext , $ type , $ rootValue , $ path , $ fields ) ;
179
+ return $ finalResults ;
176
180
}
177
181
178
182
@@ -221,15 +225,34 @@ private static function getOperationRootType(Schema $schema, OperationDefinition
221
225
*/
222
226
private static function executeFieldsSerially (ExecutionContext $ exeContext , ObjectType $ parentType , $ sourceValue , $ path , $ fields )
223
227
{
228
+ if (self ::isThenable ($ sourceValue )) {
229
+ return $ sourceValue ->then (function ($ resolvedSourceValue ) use ($ exeContext , $ parentType , $ path , $ fields ) {
230
+ return self ::executeFieldsSerially ($ exeContext , $ parentType , $ resolvedSourceValue , $ path , $ fields );
231
+ });
232
+ }
233
+
224
234
$ results = [];
225
- foreach ($ fields as $ responseName => $ fieldNodes ) {
235
+
236
+ $ process = function ($ responseName , $ fieldNodes , $ results ) use ($ path , $ exeContext , $ parentType , $ sourceValue ) {
226
237
$ fieldPath = $ path ;
227
238
$ fieldPath [] = $ responseName ;
228
239
$ result = self ::resolveField ($ exeContext , $ parentType , $ sourceValue , $ fieldNodes , $ fieldPath );
229
240
230
- if ($ result !== self ::$ UNDEFINED ) {
231
- // Undefined means that field is not defined in schema
232
- $ results [$ responseName ] = $ result ;
241
+ // Undefined means that field is not defined in schema
242
+ if ($ result === self ::$ UNDEFINED ) {
243
+ return $ results ;
244
+ }
245
+ $ results [$ responseName ] = $ result ;
246
+ return $ results ;
247
+ };
248
+
249
+ foreach ($ fields as $ responseName => $ fieldNodes ) {
250
+ if (self ::isThenable ($ results )) {
251
+ $ results = $ results ->then (function ($ resolvedResults ) use ($ responseName , $ fieldNodes , $ process ) {
252
+ return $ process ($ responseName , $ fieldNodes , $ resolvedResults );
253
+ });
254
+ } else {
255
+ $ results = $ process ($ responseName , $ fieldNodes , $ results );
233
256
}
234
257
}
235
258
// see #59
@@ -245,12 +268,9 @@ private static function executeFieldsSerially(ExecutionContext $exeContext, Obje
245
268
*/
246
269
private static function executeFields (ExecutionContext $ exeContext , ObjectType $ parentType , $ source , $ path , $ fields )
247
270
{
248
- // Native PHP doesn't support promises.
249
- // Custom executor should be built for platforms like ReactPHP
250
271
return self ::executeFieldsSerially ($ exeContext , $ parentType , $ source , $ path , $ fields );
251
272
}
252
273
253
-
254
274
/**
255
275
* Given a selectionSet, adds all of the fields in that selection to
256
276
* the passed in map of fields, and returns it at the end.
@@ -529,14 +549,26 @@ private static function completeValueCatchingError(
529
549
// Otherwise, error protection is applied, logging the error and resolving
530
550
// a null value for this field if one is encountered.
531
551
try {
532
- return self ::completeValueWithLocatedError (
552
+ $ completed = self ::completeValueWithLocatedError (
533
553
$ exeContext ,
534
554
$ returnType ,
535
555
$ fieldNodes ,
536
556
$ info ,
537
557
$ path ,
538
558
$ result
539
559
);
560
+ if (self ::isThenable ($ completed )) {
561
+ // If `completeValueWithLocatedError` returned a rejected promise, log
562
+ // the rejection error and resolve to null.
563
+ // Note: we don't rely on a `catch` method, but we do expect "thenable"
564
+ // to take a second callback for the error case.
565
+ return $ completed ->then (null , function ($ err ) use ($ exeContext ) {
566
+ $ exeContext ->addError ($ err );
567
+ return null ;
568
+ });
569
+ }
570
+
571
+ return $ completed ;
540
572
} catch (Error $ err ) {
541
573
// If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
542
574
// and return null.
@@ -545,7 +577,6 @@ private static function completeValueCatchingError(
545
577
}
546
578
}
547
579
548
-
549
580
/**
550
581
* This is a small wrapper around completeValue which annotates errors with
551
582
* location information.
@@ -559,7 +590,7 @@ private static function completeValueCatchingError(
559
590
* @return array|null
560
591
* @throws Error
561
592
*/
562
- static function completeValueWithLocatedError (
593
+ public static function completeValueWithLocatedError (
563
594
ExecutionContext $ exeContext ,
564
595
Type $ returnType ,
565
596
$ fieldNodes ,
@@ -569,14 +600,20 @@ static function completeValueWithLocatedError(
569
600
)
570
601
{
571
602
try {
572
- return self ::completeValue (
603
+ $ completed = self ::completeValue (
573
604
$ exeContext ,
574
605
$ returnType ,
575
606
$ fieldNodes ,
576
607
$ info ,
577
608
$ path ,
578
609
$ result
579
610
);
611
+ if (self ::isThenable ($ completed )) {
612
+ return $ completed ->then (null , function ($ error ) use ($ fieldNodes , $ path ) {
613
+ throw Error::createLocatedError ($ error , $ fieldNodes , $ path );
614
+ });
615
+ }
616
+ return $ completed ;
580
617
} catch (\Exception $ error ) {
581
618
throw Error::createLocatedError ($ error , $ fieldNodes , $ path );
582
619
}
@@ -622,6 +659,20 @@ private static function completeValue(
622
659
&$ result
623
660
)
624
661
{
662
+ // If result is a Promise, apply-lift over completeValue.
663
+ if (self ::isThenable ($ result )) {
664
+ return $ result ->then (function ($ resolved ) use ($ exeContext , $ returnType , $ fieldNodes , $ info , $ path ) {
665
+ return self ::completeValue (
666
+ $ exeContext ,
667
+ $ returnType ,
668
+ $ fieldNodes ,
669
+ $ info ,
670
+ $ path ,
671
+ $ resolved
672
+ );
673
+ });
674
+ }
675
+
625
676
if ($ result instanceof \Exception) {
626
677
throw $ result ;
627
678
}
@@ -893,6 +944,27 @@ private static function inferTypeOf($value, $context, ResolveInfo $info, Abstrac
893
944
return null ;
894
945
}
895
946
947
+ private static function isThenable ($ value )
948
+ {
949
+ return ($ value instanceof PromiseInterface);
950
+ }
951
+
952
+ private static function completePromiseIfNeeded ($ value )
953
+ {
954
+ if (self ::isThenable ($ value )) {
955
+ $ results = self ::completePromiseIfNeeded ($ value ->wait ());
956
+ return $ results ;
957
+ } elseif (is_array ($ value ) || $ value instanceof \Traversable) {
958
+ $ results = [];
959
+ foreach ($ value as $ key => $ item ) {
960
+ $ results [$ key ] = self ::completePromiseIfNeeded ($ item );
961
+ }
962
+ return $ results ;
963
+ }
964
+
965
+ return $ value ;
966
+ }
967
+
896
968
/**
897
969
* @deprecated as of 19.11.2016
898
970
*/
0 commit comments