@@ -359,58 +359,95 @@ private static function getFieldEntryKey(Field $node)
359359 private static function resolveField (ExecutionContext $ exeContext , ObjectType $ parentType , $ source , $ fieldASTs )
360360 {
361361 $ fieldAST = $ fieldASTs [0 ];
362- $ fieldName = $ fieldAST ->name ->value ;
363362
364- $ fieldDef = self ::getFieldDef ( $ exeContext -> schema , $ parentType , $ fieldName );
363+ $ uid = self ::getFieldUid ( $ fieldAST );
365364
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 ' ];
368372 }
373+ else {
374+ $ fieldName = $ fieldAST ->name ->value ;
369375
370- $ returnType = $ fieldDef -> getType ( );
376+ $ fieldDef = self :: getFieldDef ( $ exeContext -> schema , $ parentType , $ fieldName );
371377
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 ];
376424 } 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 ;
414451 }
415452
416453 // Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField`
@@ -554,15 +591,23 @@ private static function completeValue(ExecutionContext $exeContext, Type $return
554591 $ subFieldASTs = new \ArrayObject ();
555592 $ visitedFragmentNames = new \ArrayObject ();
556593 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+ }
566611 }
567612 }
568613
@@ -622,4 +667,15 @@ private static function getFieldDef(Schema $schema, ObjectType $parentType, $fie
622667 $ tmp = $ parentType ->getFields ();
623668 return isset ($ tmp [$ fieldName ]) ? $ tmp [$ fieldName ] : null ;
624669 }
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+ }
625681}
0 commit comments