graphQlResolverCache = $graphQlResolverCache; $this->serializer = $serializer; $this->cacheState = $cacheState; $this->resolverIdentityClassProvider = $resolverIdentityClassProvider; $this->valueProcessor = $valueProcessor; $this->keyCalculatorProvider = $keyCalculatorProvider; $this->logger = $logger; } /** * Checks for cacheability of resolver's data, and, if cacheable, loads and persists cache entry for future use * * @param ResolverInterface $subject * @param \Closure $proceed * @param Field $field * @param ContextInterface $context * @param ResolveInfo $info * @param array|null $value * @param array|null $args * @return mixed|Value */ public function aroundResolve( ResolverInterface $subject, \Closure $proceed, Field $field, $context, ResolveInfo $info, array $value = null, array $args = null ) { // even though a frontend access proxy is used to prevent saving/loading in $graphQlResolverCache when it is // disabled, it's best to return as early as possible to avoid unnecessary processing if (!$this->cacheState->isEnabled(GraphQlResolverCache::TYPE_IDENTIFIER) || $info->operation->operation !== 'query' ) { return $proceed($field, $context, $info, $value, $args); } $identityProvider = $this->resolverIdentityClassProvider->getIdentityFromResolver($subject); if (!$identityProvider) { // not cacheable; proceed return $this->executeResolver($proceed, $field, $context, $info, $value, $args); } // Cache key provider may base cache key on the parent resolver value // $value is processed on key calculation if needed try { $cacheKey = $this->prepareCacheIdentifier($subject, $args, $value); } catch (CalculationException $e) { $this->logger->warning( sprintf( "Unable to obtain cache key for %s resolver results, proceeding to invoke resolver." . "Original exception message: %s ", get_class($subject), $e->getMessage() ) ); return $this->executeResolver($proceed, $field, $context, $info, $value, $args); } $cachedResult = $this->graphQlResolverCache->load($cacheKey); if ($cachedResult !== false) { $returnValue = $this->serializer->unserialize($cachedResult); $this->valueProcessor->processCachedValueAfterLoad($info, $subject, $cacheKey, $returnValue); return $returnValue; } $returnValue = $this->executeResolver($proceed, $field, $context, $info, $value, $args); // $value (parent value) is preprocessed (hydrated) on the previous step $identities = $identityProvider->getIdentities($returnValue, $value); if (count($identities)) { $cachedValue = $returnValue; $this->valueProcessor->preProcessValueBeforeCacheSave($subject, $cachedValue); $this->graphQlResolverCache->save( $this->serializer->serialize($cachedValue), $cacheKey, $identities, false // use default lifetime directive ); unset($cachedValue); } return $returnValue; } /** * Call proceed method with context. * * @param \Closure $closure * @param Field $field * @param ContextInterface $context * @param ResolveInfo $info * @param array|null $value * @param array|null $args * @return mixed */ private function executeResolver( \Closure $closure, Field $field, ContextInterface $context, ResolveInfo $info, array &$value = null, array $args = null ) { if (is_array($value)) { $this->valueProcessor->preProcessParentValue($value); } return $closure($field, $context, $info, $value, $args); } /** * Generate cache key incorporating factors from parameters. * * @param ResolverInterface $resolver * @param array|null $args * @param array|null $value * * @return string * @throws CalculationException */ private function prepareCacheIdentifier( ResolverInterface $resolver, ?array $args, ?array $value ): string { $queryPayloadHash = sha1(get_class($resolver) . $this->serializer->serialize($args ?? [])); return GraphQlResolverCache::CACHE_TAG . '_' . $this->keyCalculatorProvider->getKeyCalculatorForResolver($resolver)->calculateCacheKey($value) . '_' . $queryPayloadHash; } }