View source
<?php
namespace Drupal\graphql\GraphQL\Execution;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\graphql\Event\OperationEvent;
use Drupal\graphql\GraphQL\Execution\ExecutionResult as CacheableExecutionResult;
use Drupal\graphql\GraphQL\Utility\DocumentSerializer;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\ExecutorImplementation;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Executor\ReferenceExecutor;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Type\Schema;
use GraphQL\Utils\AST;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class Executor implements ExecutorImplementation {
protected $pluginManager;
protected $cacheBackend;
protected $contextsManager;
protected $requestStack;
protected $dispatcher;
protected $adapter;
protected $document;
protected $context;
protected $root;
protected $variables;
protected $schema;
protected $operation;
protected $resolver;
public function __construct(CacheContextsManager $contextsManager, CacheBackendInterface $cacheBackend, RequestStack $requestStack, EventDispatcherInterface $dispatcher, PromiseAdapter $adapter, Schema $schema, DocumentNode $document, ResolveContext $context, $root, $variables, $operation, callable $resolver) {
$this->contextsManager = $contextsManager;
$this->cacheBackend = $cacheBackend;
$this->requestStack = $requestStack;
$this->dispatcher = $dispatcher;
$this->adapter = $adapter;
$this->document = $document;
$this->context = $context;
$this->root = $root;
$this->variables = $variables;
$this->schema = $schema;
$this->operation = $operation;
$this->resolver = $resolver;
}
public static function create(ContainerInterface $container, PromiseAdapter $adapter, Schema $schema, DocumentNode $document, ResolveContext $context, $root, $variables, $operation, callable $resolver) {
return new static($container
->get('cache_contexts_manager'), $container
->get('cache.graphql.results'), $container
->get('request_stack'), $container
->get('event_dispatcher'), $adapter, $schema, $document, $context, $root, $variables, $operation, $resolver);
}
public function doExecute() : Promise {
$server = $this->context
->getServer();
$operation_def = AST::getOperationAST($this->document, $this->operation);
if ($operation_def && $operation_def->operation === 'query' && !!$server
->get('caching')) {
return $this
->doExecuteCached($this
->cachePrefix());
}
return $this
->doExecuteUncached()
->then(function ($result) {
$this->context
->mergeCacheMaxAge(0);
$result = new CacheableExecutionResult($result->data, $result->errors, $result->extensions);
$result
->addCacheableDependency($this->context);
return $result;
});
}
protected function doExecuteCached($prefix) {
if ($result = $this
->cacheRead($prefix)) {
return $this->adapter
->createFulfilled($result);
}
return $this
->doExecuteUncached()
->then(function (ExecutionResult $result) use ($prefix) {
if (!empty($result->errors)) {
$this->context
->mergeCacheMaxAge(0);
}
$result = new CacheableExecutionResult($result->data, $result->errors, $result->extensions);
$result
->addCacheableDependency($this->context);
if ($result
->getCacheMaxAge() !== 0) {
$this
->cacheWrite($prefix, $result);
}
return $result;
});
}
protected function doExecuteUncached() {
$executor = ReferenceExecutor::create($this->adapter, $this->schema, $this->document, $this->root, $this->context, $this->variables, $this->operation, $this->resolver);
$event = new OperationEvent($this->context);
$this->dispatcher
->dispatch(OperationEvent::GRAPHQL_OPERATION_BEFORE, $event);
return $executor
->doExecute()
->then(function ($result) {
$event = new OperationEvent($this->context, $result);
$this->dispatcher
->dispatch(OperationEvent::GRAPHQL_OPERATION_AFTER, $event);
return $result;
});
}
protected function cachePrefix() {
$variables = $this->variables ?: [];
ksort($variables);
$extensions = $this->context
->getOperation()->extensions ?: [];
ksort($extensions);
$hash = hash('sha256', serialize([
'query' => DocumentSerializer::serializeDocument($this->document),
'variables' => $variables,
'extensions' => $extensions,
]));
return $hash;
}
protected function cacheSuffix(array $contexts = []) {
$keys = $this->contextsManager
->convertTokensToKeys($contexts)
->getKeys();
return hash('sha256', serialize($keys));
}
protected function cacheRead($prefix) {
if ($cache = $this->cacheBackend
->get("contexts:{$prefix}")) {
$suffix = $this
->cacheSuffix($cache->data ?? []);
if ($cache = $this->cacheBackend
->get("result:{$prefix}:{$suffix}")) {
$result = new CacheableExecutionResult($cache->data['data'], [], $cache->data['extensions']);
$result
->addCacheableDependency($cache->data['metadata']);
return $result;
}
}
return NULL;
}
protected function cacheWrite($prefix, CacheableExecutionResult $result) {
$contexts = $result
->getCacheContexts();
$expire = $this
->maxAgeToExpire($result
->getCacheMaxAge());
$tags = $result
->getCacheTags();
$suffix = $this
->cacheSuffix($contexts);
$metadata = new CacheableMetadata();
$metadata
->addCacheableDependency($result);
$cache = [
'data' => $result->data,
'extensions' => $result->extensions,
'metadata' => $metadata,
];
$this->cacheBackend
->setMultiple([
"contexts:{$prefix}" => [
'data' => $contexts,
'expire' => $expire,
'tags' => $tags,
],
"result:{$prefix}:{$suffix}" => [
'data' => $cache,
'expire' => $expire,
'tags' => $tags,
],
]);
return $this;
}
protected function maxAgeToExpire($maxAge) {
$time = $this->requestStack
->getMasterRequest()->server
->get('REQUEST_TIME');
return $maxAge === Cache::PERMANENT ? Cache::PERMANENT : (int) $time + $maxAge;
}
}