对于插件,我需要动态地操纵产品价格。如果客户使用参数?price=special
调用产品URL,则如果设置了特定的会话密钥,则需要在详细信息页面和商店各处的每个后续请求(CMS页面上的滑块、列表、详细信息页面、购物车、搜索等)上操纵价格。
该逻辑可以工作(在没有HTTP缓存的开发环境中),但在生产环境中不起作用。
如果我为客户A操纵产品价格,产品页面将缓存降低的价格,客户B将获得客户A的价格。
当客户有匹配的会话密钥时,我修饰了CacheStateValidator
以跳过HTTP缓存,但即使使用了停用的HTTP缓存,当EntityLoadedEvent
(product.loaded
)已经从另一个客户缓存时,也不会调度它。
我还尝试了CustomProductPriceCalculator
(如关于价格计算的开发文档中所示),但缓存问题仍然存在。
- 禁用第二个(ObjectCache?)缓存的最佳方法是什么
- 动态操纵单一产品价格的最佳方式是什么
更新08.04.2023
我为以下事件创建了一个订阅者,并尝试禁用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();
}
}
当我在两种浏览器(Firefox、Chrome)中打开不带会话密钥的产品时,我看到两种浏览器的价格都是正常的。如果我在Chrome中添加会话密钥并重新加载两个浏览器,我会在两个浏览器中看到更新后的价格。
我用xdebug测试了Chrome请求,并调用了$event->disableCaching();
方法。
正在运行的第二个缓存不是HTTP-Full-Page缓存,而是Store-API路由级别的缓存。
在你的情况下,这可能是ProductDetailRoute
(或者ProductListingRoute
,当你想更改列表中的价格时,答案可以很容易地适用于其他路线)
Store-API路由由例如处理该特定路由的高速缓存的CachedProductDetailRoute
来装饰。您遇到的问题是缓存层不知道您的修改,因此总是缓存结果。但是所有缓存的Store-API路由都会调度一个StoreApiRouteCacheKeyEvent
,在您的情况下,它将是ProductDetailRouteCacheKeyEvent
。在这些事件中,您可以根据请求、上下文和条件向缓存键添加部分。因此,您应该能够在缓存密钥中添加一个部分,以区分应该获得降价的用户和应该获得正常价格的用户:
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');
}
}
这样,您基本上为路由建立了两个缓存,一个用于具有会话密钥的客户,另一个用于没有会话密钥的用户。
但是,如果价格是动态的,例如每个拥有会话密钥的客户都有自己的价格,那么您需要为每个客户建立一个缓存,例如使用
$event->addPart($sessionId);
但请记住,这将是非常低效的,因为缓存命中率将非常低。在这种情况下,您可以通过调整第一个示例中的代码来为所有具有动态价格的客户禁用缓存:
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();
}
}
这样,所有没有会话密钥的客户都将获得"会话密钥";正常的";缓存的结果,只有具有会话密钥的客户才会完全绕过缓存。
根据您的价格动态(例如,每个客户或多个客户的价格相同),您可以使用我在这里提供的不同解决方案(例如,禁用特定客户的缓存,或建立第二个缓存)
这些想法可以(或需要)适用于其他路由(例如列表路由)和整个HTTP Full Page缓存。