2

For a plugin I need to manipulate a product price dynamically. I a customer calls the product URL with the parameter ?price=special the price needs to be manipulated on the detail page and on every following request everywhere in the shop (slider on CMS pages, listing, detail page, cart, search, etc.) if a specific session key is set.

The logic works (when in dev environment with no HTTP cache), but not in production.

If I manipulate the product price for customer A, the product page will be cached with the lowered price and customer B will get the price of customer A.

I decorated the CacheStateValidator to skip the HTTP cache when the customer has a matching session key, but even with the deactivated HTTP cache my EntityLoadedEvent (product.loaded) is not dispatched when it is already cached from another customer.

I also tried a CustomProductPriceCalculator (as seen in the Dev-Docs about Price Calculation), but the caching problems persist.

  1. What would be the best way to disabled this second (ObjectCache?) cache?
  2. What is the best way to manipulate single product prices dynamically?

Update 08.04.2023

I created a subscriber for the following events and tried to disable the StoreApiRouteCache:

    public static function getSubscribedEvents(): array
    {
        return [
            ProductDetailRouteCacheKeyEvent::class => 'onCacheKeyEvent',
            ProductListingRouteCacheKeyEvent::class => 'onCacheKeyEvent',
            ProductSearchRouteCacheKeyEvent::class => 'onCacheKeyEvent',
            ProductSuggestRouteCacheKeyEvent::class => 'onCacheKeyEvent',
            CrossSellingRouteCacheKeyEvent::class => 'onCacheKeyEvent',
        ];
    }

    public function onCacheKeyEvent(StoreApiRouteCacheKeyEvent $event): void
    {
        if ($this->sessionKeyExists) {
            $event->disableCaching();
        }
    }

When I open the product without the session key in two browsers (Firefox, Chrome) I see the normal price in both browsers. If I add the session key in Chrome and reload both browsers, I see the updated price in both browsers.

I tested the Chrome requests with xdebug and the $event->disableCaching(); method is called.

Dennis
  • 59
  • 1
  • 4

1 Answers1

3

The second cache you are running into is not the HTTP-Full-Page cache, but it is the cache on Store-API route level.

In your case that is probable the ProductDetailRoute (or maybe ProductListingRoute when you wanna change the prices in the listing, the answer can be easily adapted for other routes)

The Store-API routes are decorated by a e.g. CachedProductDetailRoute that handles caching for this specific route. The issue you experience for that is that the caching layer does not know your modifications and thus always caches the result. But all cached Store-API routes dispatch an StoreApiRouteCacheKeyEvent, in your case that would be the ProductDetailRouteCacheKeyEvent. In those events you can add parts to the cache key based on the request, the context and the criteria. So you should be able to add a part to the cache key to differentiate between users that should get the reduced prices and those that should get the normal prices:

public function onCacheKeyEvent(ProductDetailRouteCacheKeyEvent $event): void
{
    $request = $event->getRequest();
    // pseudo code based on the info in your question 
    if ($request->getSession()->has('mySessionKey)) {
       $event->addPart('reducedPricesActive');
    }
}

With this you basically build up two caches for the route, one for the customers with the session key and one for the ones without the session key.

But if the prices are that dynamic that e.g. each customer with a session key has it's own price you would need to build a cache per customer e.g. with

$event->addPart($sessionId);

But keep in mind that will be pretty inefficient as the cache-hit rate would be pretty low. In that case you could disable the caching instead for all the customers that have a dynamic price, by adapting the code from the first example:

public function onCacheKeyEvent(ProductDetailRouteCacheKeyEvent $event): void
{
    $request = $event->getRequest();
    // pseudo code based on the info in your question 
    if ($request->getSession()->has('mySessionKey)) {
       $event->disableCaching();
    }
}

That way all customers without the session key would get the "normal" cached results, only customers with the session key will bypass the cache completely.

Based on how dynamic your prices are (e.g. per customer or have multiple customer the same price) you can use a different solution i presented here (e.g. disable caching for specific customer, or build a second cache)

Those ideas can (or need) to be adapted for other routes (e.g. the listing route) and the overall HTTP-Full-Page cache as well.

j_elfering
  • 2,707
  • 6
  • 15