DEV Community

Life is Good
Life is Good

Posted on

Optimizing Magento Full Page Cache for View Models with Custom Cache Tags in Hyvä

Problem

Magento's full page cache significantly boosts performance. However, dynamic data rendered through view models can become stale if the cache isn't invalidated precisely. Standard cache invalidation strategies often operate at a broader scope, potentially leading to either excessive cache flushing or, worse, displaying outdated information to users. This creates a challenging balance between performance and data accuracy.

Solution

The solution involves leveraging custom cache tags for your view models. By assigning unique, data-specific tags to the output of a view model, you gain granular control over cache invalidation. This means you can precisely target and flush only the cache entries related to that specific view model's data when it changes, without affecting unrelated cached content. Hyvä Themes, built on Magento, fully supports and encourages this approach for optimal caching.

Implementation

To implement custom cache tags for a view model, follow these steps:

  1. Define Cache Tags in Your View Model:
    First, your view model needs to declare the cache tags it will use. These tags should logically represent the data dependencies of the view model.

    php
    <?php

    declare(strict_types=1);

    namespace Vendor\Module\ViewModel;

    use Magento\Framework\View\Element\Block\ArgumentInterface;
    use Magento\Framework\DataObject\IdentityInterface; // Important for cache tags

    class MyCustomData implements ArgumentInterface, IdentityInterface
    {
    public const CACHE_TAG = 'vendor_module_custom_data';

    private \Vendor\Module\Model\DataRepository $dataRepository;
    
    public function __construct(
        \Vendor\Module\Model\DataRepository $dataRepository
    ) {
        $this->dataRepository = $dataRepository;
    }
    
    public function getSomeDynamicData(): array
    {
        // Fetch data that needs specific caching
        return $this->dataRepository->getLatestItems();
    }
    
    /**
     * Get identities (cache tags) for the view model.
     * This method is crucial for cache invalidation.
     *
     * @return string[]
     */
    public function getIdentities(): array
    {
        // Add the general cache tag for this view model.
        // You can also add specific IDs if the data is tied to a particular entity.
        return [self::CACHE_TAG];
    }
    

    }

    In this example, IdentityInterface is implemented, and getIdentities() returns [self::CACHE_TAG]. This tells Magento to associate the output of any block using this view model with vendor_module_custom_data cache tag.

  2. Assign the View Model to a Block in Layout XML:
    Ensure your view model is correctly assigned to a block in your layout XML. The block itself must be cacheable.

    xml






    Vendor\Module\ViewModel\MyCustomData




    The cacheable="true" attribute on the block is essential for the cache tags to take effect.

  3. Use the View Model in Your Template:
    Access the view model's data in your PHTML template.

    phtml

    <?php
    /** @var \Vendor\Module\ViewModel\MyCustomData $viewModel */
    $viewModel = $block->getData('my_data_view_model');
    ?>

    Latest Items

      getSomeDynamicData() as $item): ?>
    • = $block->escapeHtml($item['name']) ?>
  4. Invalidate the Cache Tag Programmatically:
    The crucial step is to invalidate this specific cache tag when the underlying data changes. This is typically done after a save, update, or delete operation on the data that the view model displays.

    php
    <?php

    declare(strict_types=1);

    namespace Vendor\Module\Observer;

    use Magento\Framework\Event\ObserverInterface;
    use Magento\Framework\Event\Observer;
    use Magento\Framework\App\Cache\TypeListInterface;
    use Vendor\Module\ViewModel\MyCustomData;

    class InvalidateMyCustomDataCache implements ObserverInterface
    {
    private TypeListInterface $cacheTypeList;

    public function __construct(
        TypeListInterface $cacheTypeList
    ) {
        $this->cacheTypeList = $cacheTypeList;
    }
    
    public function execute(Observer $observer): void
    {
        // Assuming this observer is triggered after saving the data
        // that MyCustomData view model depends on.
        // For example, listen to 'vendor_module_data_save_after' or similar event.
    
        $this->cacheTypeList->invalidate(MyCustomData::CACHE_TAG);
        // If you had specific entity IDs, you could invalidate those too:
        // $this->cacheTypeList->invalidate(MyCustomData::CACHE_TAG . '_' . $entityId);
    }
    

    }

    You would register this observer in events.xml to listen to an appropriate event (e.g., model_save_after for your specific data model). Alternatively, you can inject TypeListInterface into your data model's save() method or a plugin for it.

    For more advanced scenarios, especially when dealing with specific entity IDs, the getIdentities() method of your view model can return an array like [self::CACHE_TAG, self::CACHE_TAG . '_' . $entityId]. This allows for even finer-grained invalidation.

    For comprehensive documentation on advanced caching topics, including view model cache tags and identity mapping, refer to the official Hyvä documentation: https://hyvathemes.com/docs/advanced-topics/view-model-cache-tags/

Context

This approach works because Magento's full page cache (FPC) is tag-based. When a page is rendered and cached, Magento associates the generated HTML with a set of cache tags. These tags include generic ones (like CMS_PAGE or CATALOG_PRODUCT), and importantly, any custom tags returned by IdentityInterface implementations within the blocks or view models used on that page.

When an event triggers a cache invalidation, Magento doesn't just clear the entire FPC. Instead, it looks for specific tags. If you invalidate vendor_module_custom_data, Magento identifies all cached pages that have vendor_module_custom_data associated with them and marks them as invalid. The next time a user requests one of these pages, it will be re-generated from scratch, ensuring the latest data is displayed.

By implementing IdentityInterface in your view models and returning specific cache tags, you are effectively telling Magento: "This piece of data, when rendered, depends on these specific identifiers. If any of these identifiers change, this cached content needs to be refreshed." This precision prevents unnecessary cache flushes, which would degrade performance, while simultaneously guaranteeing that users always see up-to-date information for dynamic elements. It's a fundamental strategy for building high-performance, data-accurate Magento applications, especially within the context of a highly optimized frontend like Hyvä Themes.

Top comments (0)