@@ -359,58 +359,95 @@ private static function getFieldEntryKey(Field $node)
359
359
private static function resolveField (ExecutionContext $ exeContext , ObjectType $ parentType , $ source , $ fieldASTs )
360
360
{
361
361
$ fieldAST = $ fieldASTs [0 ];
362
- $ fieldName = $ fieldAST ->name ->value ;
363
362
364
- $ fieldDef = self ::getFieldDef ( $ exeContext -> schema , $ parentType , $ fieldName );
363
+ $ uid = self ::getFieldUid ( $ fieldAST );
365
364
366
- if (!$ fieldDef ) {
367
- return self ::$ UNDEFINED ;
365
+ // Get memoized variables if they exist
366
+ if (isset ($ exeContext ->memoized ['resolveField ' ][$ uid ])) {
367
+ $ memoized = $ exeContext ->memoized ['resolveField ' ][$ uid ];
368
+ $ fieldDef = $ memoized ['fieldDef ' ];
369
+ $ returnType = $ fieldDef ->getType ();
370
+ $ args = $ memoized ['args ' ];
371
+ $ info = $ memoized ['info ' ];
368
372
}
373
+ else {
374
+ $ fieldName = $ fieldAST ->name ->value ;
369
375
370
- $ returnType = $ fieldDef -> getType ( );
376
+ $ fieldDef = self :: getFieldDef ( $ exeContext -> schema , $ parentType , $ fieldName );
371
377
372
- if (isset ($ fieldDef ->resolveFn )) {
373
- $ resolveFn = $ fieldDef ->resolveFn ;
374
- } else if (isset ($ parentType ->resolveFieldFn )) {
375
- $ resolveFn = $ parentType ->resolveFieldFn ;
378
+ if (!$ fieldDef ) {
379
+ return self ::$ UNDEFINED ;
380
+ }
381
+
382
+ $ returnType = $ fieldDef ->getType ();
383
+
384
+ // Build hash of arguments from the field.arguments AST, using the
385
+ // variables scope to fulfill any variable references.
386
+ // TODO: find a way to memoize, in case this field is within a List type.
387
+ $ args = Values::getArgumentValues (
388
+ $ fieldDef ->args ,
389
+ $ fieldAST ->arguments ,
390
+ $ exeContext ->variableValues
391
+ );
392
+
393
+ // The resolve function's optional third argument is a collection of
394
+ // information about the current execution state.
395
+ $ info = new ResolveInfo ([
396
+ 'fieldName ' => $ fieldName ,
397
+ 'fieldASTs ' => $ fieldASTs ,
398
+ 'returnType ' => $ returnType ,
399
+ 'parentType ' => $ parentType ,
400
+ 'schema ' => $ exeContext ->schema ,
401
+ 'fragments ' => $ exeContext ->fragments ,
402
+ 'rootValue ' => $ exeContext ->rootValue ,
403
+ 'operation ' => $ exeContext ->operation ,
404
+ 'variableValues ' => $ exeContext ->variableValues ,
405
+ ]);
406
+
407
+ // Memoizing results for same query field
408
+ // (useful for lists when several values are resolved against the same field)
409
+ if ($ returnType instanceof ObjectType) {
410
+ $ memoized = $ exeContext ->memoized ['resolveField ' ][$ uid ] = [
411
+ 'fieldDef ' => $ fieldDef ,
412
+ 'args ' => $ args ,
413
+ 'info ' => $ info ,
414
+ 'results ' => new \SplObjectStorage
415
+ ];
416
+ }
417
+ }
418
+
419
+ // When source value is object it is possible to memoize certain subset of results
420
+ $ isObject = is_object ($ source );
421
+
422
+ if ($ isObject && isset ($ memoized ['results ' ][$ source ])) {
423
+ $ result = $ exeContext ->memoized ['resolveField ' ][$ uid ]['results ' ][$ source ];
376
424
} else {
377
- $ resolveFn = self ::$ defaultResolveFn ;
378
- }
379
-
380
- // Build hash of arguments from the field.arguments AST, using the
381
- // variables scope to fulfill any variable references.
382
- // TODO: find a way to memoize, in case this field is within a List type.
383
- $ args = Values::getArgumentValues (
384
- $ fieldDef ->args ,
385
- $ fieldAST ->arguments ,
386
- $ exeContext ->variableValues
387
- );
388
-
389
- // The resolve function's optional third argument is a collection of
390
- // information about the current execution state.
391
- $ info = new ResolveInfo ([
392
- 'fieldName ' => $ fieldName ,
393
- 'fieldASTs ' => $ fieldASTs ,
394
- 'returnType ' => $ returnType ,
395
- 'parentType ' => $ parentType ,
396
- 'schema ' => $ exeContext ->schema ,
397
- 'fragments ' => $ exeContext ->fragments ,
398
- 'rootValue ' => $ exeContext ->rootValue ,
399
- 'operation ' => $ exeContext ->operation ,
400
- 'variableValues ' => $ exeContext ->variableValues ,
401
- ]);
402
-
403
- // Get the resolve function, regardless of if its result is normal
404
- // or abrupt (error).
405
- $ result = self ::resolveOrError ($ resolveFn , $ source , $ args , $ info );
406
-
407
- return self ::completeValueCatchingError (
408
- $ exeContext ,
409
- $ returnType ,
410
- $ fieldASTs ,
411
- $ info ,
412
- $ result
413
- );
425
+ if (isset ($ fieldDef ->resolveFn )) {
426
+ $ resolveFn = $ fieldDef ->resolveFn ;
427
+ } else if (isset ($ parentType ->resolveFieldFn )) {
428
+ $ resolveFn = $ parentType ->resolveFieldFn ;
429
+ } else {
430
+ $ resolveFn = self ::$ defaultResolveFn ;
431
+ }
432
+
433
+ // Get the resolve function, regardless of if its result is normal
434
+ // or abrupt (error).
435
+ $ result = self ::resolveOrError ($ resolveFn , $ source , $ args , $ info );
436
+
437
+ $ result = self ::completeValueCatchingError (
438
+ $ exeContext ,
439
+ $ returnType ,
440
+ $ fieldASTs ,
441
+ $ info ,
442
+ $ result
443
+ );
444
+
445
+ if ($ isObject && isset ($ memoized ['results ' ])) {
446
+ $ exeContext ->memoized ['resolveField ' ][$ uid ]['results ' ][$ source ] = $ result ;
447
+ }
448
+ }
449
+
450
+ return $ result ;
414
451
}
415
452
416
453
// Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField`
@@ -554,15 +591,23 @@ private static function completeValue(ExecutionContext $exeContext, Type $return
554
591
$ subFieldASTs = new \ArrayObject ();
555
592
$ visitedFragmentNames = new \ArrayObject ();
556
593
for ($ i = 0 ; $ i < count ($ fieldASTs ); $ i ++) {
557
- $ selectionSet = $ fieldASTs [$ i ]->selectionSet ;
558
- if ($ selectionSet ) {
559
- $ subFieldASTs = self ::collectFields (
560
- $ exeContext ,
561
- $ runtimeType ,
562
- $ selectionSet ,
563
- $ subFieldASTs ,
564
- $ visitedFragmentNames
565
- );
594
+ // Get memoized value if it exists
595
+ $ uid = self ::getFieldUid ($ fieldASTs [$ i ]);
596
+ if (isset ($ exeContext ->memoized ['collectSubFields ' ][$ uid ][$ runtimeType ->name ])) {
597
+ $ subFieldASTs = $ exeContext ->memoized ['collectSubFields ' ][$ uid ][$ runtimeType ->name ];
598
+ }
599
+ else {
600
+ $ selectionSet = $ fieldASTs [$ i ]->selectionSet ;
601
+ if ($ selectionSet ) {
602
+ $ subFieldASTs = self ::collectFields (
603
+ $ exeContext ,
604
+ $ runtimeType ,
605
+ $ selectionSet ,
606
+ $ subFieldASTs ,
607
+ $ visitedFragmentNames
608
+ );
609
+ $ exeContext ->memoized ['collectSubFields ' ][$ uid ][$ runtimeType ->name ] = $ subFieldASTs ;
610
+ }
566
611
}
567
612
}
568
613
@@ -622,4 +667,15 @@ private static function getFieldDef(Schema $schema, ObjectType $parentType, $fie
622
667
$ tmp = $ parentType ->getFields ();
623
668
return isset ($ tmp [$ fieldName ]) ? $ tmp [$ fieldName ] : null ;
624
669
}
670
+
671
+ /**
672
+ * Get an unique identifier for a FieldAST.
673
+ *
674
+ * @param object $fieldAST
675
+ * @return string
676
+ */
677
+ private static function getFieldUid ($ fieldAST )
678
+ {
679
+ return $ fieldAST ->loc ->start . '- ' . $ fieldAST ->loc ->end ;
680
+ }
625
681
}
0 commit comments