Search code examples
shopware6shopware6-app

How to correctly invalidate the cache in a Shopware 6 app script


With custom entities it is possible to create your own data structures based on shopware 6's app system. So I went ahead and created my own entity which has a CRUD within the storefront. I am loading my data via twig macro on the product-page-loaded hook:

{# located in custom/apps/MyApp/Resources/scripts/include/my-script.twig #}
{# @var services \Shopware\Core\Framework\Script\ServiceStubs #}

{% macro load(salesChannelId, header) %}
    {% set criteria = {
        'filter': [
            { 'type': 'equals', 'field': 'salesChannelId', 'value': salesChannelId },
        ]
    } %}

    {% set myEntity = services.repository.search('ce_atl_my_entity', criteria) %}

    {% if myEntity.first is not empty %}
        {% do header.addExtension('atlMyExtension', myEntity.first) %}
    {% endif %}
{% endmacro %}

Now the user is able to adjust this data I just loaded via the following custom storefront endpoint:

{# located in custom/apps/MyApp/Resources/scripts/storefront-my-script/my-script.twig #}
{# @var services \Shopware\Core\Framework\Script\ServiceStubs #}

{% do services.writer.upsert('ce_atl_my_entity', [
    {
        'id': hook.request['id'],
        'rating': hook.request['rating'],
        'url': hook.request['url'],
        'userRatingsTotal': hook.request['user_ratings_total'],
        'salesChannelId': hook.request['sales_channel_id']
    }
]) %}

{% set response = services.response.json({}) %}
{% do response.cache.tag('my-entity-' ~ hook.request['id']) %}

{% do hook.setResponse(response) %}

Once data gets updated I need to invalidate the cache, otherwise I am fetching stale cache items on the next product page load. Cache invalidation is described here. However, it doesn't seem to work for me.

Here is my cache invalidation script:

{# located in custom/apps/MyApp/Resources/scripts/cache-invalidation/my-invalidation-script.twig #}

{# @var services \Shopware\Core\Framework\Script\ServiceStubs #}

{% set ids = hook.event.getIds('ce_atl_my_entity') %}

{% if ids.empty %}
    {% return %}
{% endif %}

{% set tags = [] %}
{% for id in ids %}
    {% set tags = tags|merge(['my-entity-' ~ id]) %}
{% endfor %}

{% do services.cache.invalidate(tags) %}

What am I missing?


Solution

  • Using custom cache tags only works when you have access to the response, which you don't have with the product-page-loaded hook.

    You can invalidate the product detail route cache by using this pattern for the cache tag:

    product-detail-route-{{parentId || id}}
    

    So if the product has a parentId use that as suffix, otherwise use the id. That probably won't help you much though, as you won't have the product ids in the invalidation script, when you persist your custom entity.

    It might honestly be easiest to simply pass the product id to your custom endpoint, to also persist the product together with your custom entity. This will invalidate the cache of the corresponding detail pages automatically.

    {% do services.writer.upsert('ce_atl_my_entity', [
        {
            'id': hook.request['id']
        }
    ]) %}
    
    {# then also persist the product to invalidate the detail page cache #}
    
    {% do services.writer.upsert('product', [
        {
            'id': hook.request['productId']
        }
    ]) %}
    

    For custom endpoints:

    You are supposed to choose the tag yourself. You must therefore also tag the response to be cached yourself. Given you have a custom storefront endpoint:

    {# Resources/scripts/storefront-custom-endpoint/my-example-script.twig #}
    
    {% set criteria = {
        'associations': {
            'product': {}
        }
    } %}
    {% set customEntity = services.repository.search('ce_atl_my_entity', criteria).first %}
    
    {% do hook.page.addExtension('myCustomEntity', customEntity) %}
    
    {% set response = services.response.render('@MyApp/storefront/page/custom-page/index.html.twig', { 'page': hook.page }) %}
    
    {# here you add your own cache tag #}
    {% do response.cache.tag('my-custom-tag-' ~ customEntity.id) %}
    
    {% do hook.setResponse(response) %}
    

    Then the responses of that endpoint get cached with my-custom-tag-{id} and you can use the same pattern to invalidate the cached responses.

    {# Resources/scripts/cache-invalidation/my-invalidation-script.twig #}
    {# ... #}
    
    {% set tags = [] %}
    {% for id in ids %}
        {% set tags = tags|merge(['my-custom-tag-' ~ id]) %}
    {% endfor %}
    
    {% do services.cache.invalidate(tags) %}