commercetools-8.x-1.2-alpha1/src/EventSubscriber/CommercetoolsGraphQlCacheSubscriber.php
src/EventSubscriber/CommercetoolsGraphQlCacheSubscriber.php
<?php
declare(strict_types=1);
namespace Drupal\commercetools\EventSubscriber;
use Drupal\commercetools\Cache\CacheableCommercetoolsGraphQlResponse;
use Drupal\commercetools\Event\CommercetoolsGraphQlOperationEvent;
use Drupal\commercetools\Event\CommercetoolsGraphQlOperationResultEvent;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Replace tokens in the variable array on values.
*/
class CommercetoolsGraphQlCacheSubscriber implements EventSubscriberInterface {
/**
* Array of parsed query objects.
*
* @var array
*/
protected array $queryObjects;
/**
* CommercetoolsGraphQlCacheSubscriber constructor.
*
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cacheTagsInvalidator
* The cache tags invalidator.
*/
public function __construct(
protected CacheTagsInvalidatorInterface $cacheTagsInvalidator,
) {}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
CommercetoolsGraphQlOperationEvent::class => ['onExecute'],
CommercetoolsGraphQlOperationResultEvent::class => ['afterExecute'],
];
}
/**
* Before GraphQl execution event handler.
*
* @throws \GraphQL\Error\SyntaxError
*/
public function onExecute(CommercetoolsGraphQlOperationEvent $event): void {
$query = $event->query;
$variables = $event->variables;
$cacheableMetadata = $event->cacheableMetadata;
$queryObject = $this->getQueryObject($query);
$queryRootNodes = $queryObject->toArray()['definitions'];
$cacheableMetadata->addCacheTags([
// @todo Maybe get rid of the general tag and keep only configuration.
CacheableCommercetoolsGraphQlResponse::CACHE_TAG_GENERAL,
CacheableCommercetoolsGraphQlResponse::CACHE_TAG_CONFIGURATION,
CacheableCommercetoolsGraphQlResponse::CACHE_TAG_API_CONFIGURATION,
]);
for ($i = 0; $i < $queryRootNodes->count(); $i++) {
$node = $queryRootNodes->offsetGet($i);
// Getting the operation name.
$operation = $node->selectionSet->selections->offsetGet(0)->name->value;
switch ($operation) {
case 'productProjectionSearch':
case 'products':
// @todo Rework this to proper parsing of where and use the list tag
// for wide searches.
if (isset($variables['limit']) && $variables['limit'] > 1) {
// Search for multiple products, so use cache by product list.
$cacheableMetadata->addCacheTags([CacheableCommercetoolsGraphQlResponse::CACHE_TAG_PRODUCT_LIST]);
}
break;
case 'cart':
$cacheableMetadata->addCacheTags([CacheableCommercetoolsGraphQlResponse::CACHE_TAG_CART_PREFIX . $variables['id']]);
break;
case 'customers':
// Cache load by customer ID queries.
if (isset($variables['id'])) {
$cacheableMetadata->addCacheTags([CacheableCommercetoolsGraphQlResponse::CACHE_TAG_CUSTOMER_PREFIX . $variables['id']]);
}
else {
// Disable caching for other queries.
$cacheableMetadata->setCacheMaxAge(0);
}
break;
default:
// Disabling cache for not-known operations.
$cacheableMetadata->setCacheMaxAge(0);
}
}
}
/**
* After GraphQl execution event handler.
*
* @throws \GraphQL\Error\SyntaxError
*/
public function afterExecute(CommercetoolsGraphQlOperationResultEvent $event): void {
$result = &$event->result;
$cacheableMetadata = $event->cacheableMetadata;
$query = $event->query;
$queryObject = $this->getQueryObject($query);
$queryRootNodes = $queryObject->toArray()['definitions'];
for ($i = 0; $i < $queryRootNodes->count(); $i++) {
$node = $queryRootNodes->offsetGet($i);
// Getting the operation name.
$operation = $node->selectionSet->selections->offsetGet(0)->name->value;
switch ($operation) {
case 'productProjectionSearch':
case 'products':
foreach ($result[$operation]['results'] as $product) {
$cacheableMetadata->addCacheTags([CacheableCommercetoolsGraphQlResponse::CACHE_TAG_PRODUCT_PREFIX . $product['id']]);
}
break;
case 'orders':
foreach ($result['orders']['results'] as $order) {
$cacheableMetadata->addCacheTags([CacheableCommercetoolsGraphQlResponse::CACHE_TAG_ORDERS_PREFIX . $order['id']]);
}
break;
case 'customers':
foreach ($result['customers']['results'] as $customer) {
$cacheableMetadata->addCacheTags([CacheableCommercetoolsGraphQlResponse::CACHE_TAG_CUSTOMER_PREFIX . $customer['id']]);
}
break;
// Invalidate cache related to a mutation request.
case 'updateCart':
case 'deleteCart':
$this->cacheTagsInvalidator->invalidateTags([CacheableCommercetoolsGraphQlResponse::CACHE_TAG_CART_PREFIX . $result[$operation]['id']]);
break;
case 'updateCustomer':
case 'deleteCustomer':
$this->cacheTagsInvalidator->invalidateTags([CacheableCommercetoolsGraphQlResponse::CACHE_TAG_CUSTOMER_PREFIX . $result[$operation]['id']]);
break;
default:
continue 2;
}
}
}
/**
* Get parsed query object.
*
* @param string $query
* A GraphQL query.
*
* @return \GraphQL\Language\AST\DocumentNode
* Parsed GraphQL object.
*
* @throws \GraphQL\Error\SyntaxError
*/
protected function getQueryObject(string $query): DocumentNode {
// @todo Find a more lightweight library to parse the query.
$this->queryObjects[$query] ??= Parser::parse($query);
return $this->queryObjects[$query];
}
}
