Your Own Cache Type

Declare a cache type in cache.xml, extend TagScope, and store computed data with a lifetime you choose.

Syntaxclass MyType extends \Magento\Framework\Cache\Frontend\Decorator\TagScope

When your module computes something expensive, do not invent a storage for it. Declare a cache type: a named slice of Magento's cache that belongs to your module. You get a checkbox in System > Cache Management, a target for cache:clean, and tag scoping, so cleaning your type cannot touch anyone else's entries.

Two files

etc/cache.xml declares the type with a name, a label, a description and the instance class. The class extends Magento\Framework\Cache\Frontend\Decorator\TagScope and takes one constructor argument, Magento\Framework\App\Cache\Type\FrontendPool. It passes $cacheFrontendPool->get(self::TYPE_IDENTIFIER) and its own tag up to the parent. TYPE_IDENTIFIER must match the name attribute exactly, or the pool returns the wrong frontend.

Lifetime

cache.xml has no lifetime attribute; the schema allows only label and description. The lifetime is the fourth argument of save($data, $identifier, array $tags = [], $lifeTime = null). Passing null takes the frontend default. Pass seconds for anything you want to expire on its own, and keep it short enough that a missed invalidation self-corrects.

Making it appear

A new module needs bin/magento setup:upgrade. An existing one needs only bin/magento cache:flush, because the type list lives in the config cache and will not list your type until that is cleared. In production mode a new class also needs bin/magento setup:di:compile.

Example

Example · php
<?php
// app/code/Vendor/PriceCache/Model/Cache/Type/PriceGrid.php
declare(strict_types=1);

namespace Vendor\PriceCache\Model\Cache\Type;

use Magento\Framework\App\Cache\Type\FrontendPool;
use Magento\Framework\Cache\Frontend\Decorator\TagScope;

class PriceGrid extends TagScope
{
    /**
     * Must match the name attribute in etc/cache.xml.
     */
    public const TYPE_IDENTIFIER = 'vendor_price_grid';

    /**
     * Every entry saved through this type carries this tag,
     * which is what scopes clean() to this type alone.
     */
    public const CACHE_TAG = 'VENDOR_PRICE_GRID';

    public function __construct(FrontendPool $cacheFrontendPool)
    {
        parent::__construct(
            $cacheFrontendPool->get(self::TYPE_IDENTIFIER),
            self::CACHE_TAG
        );
    }
}

// Injected like any other dependency. After the class is added,
// run: bin/magento cache:flush
// then: bin/magento setup:di:compile  (production mode only)
// bin/magento cache:status now lists vendor_price_grid: 1

When to use it

  • Caching a rollup that takes several queries to build, such as a per-product price or availability summary.
  • Giving support staff a Cache Management checkbox they can clear without touching the rest of the cache.
  • Holding a response from a slow third-party service for a fixed number of seconds.

More examples

The declaration

This is the whole declaration. After bin/magento cache:flush, the label and description appear as a new row in System > Cache Management and the name becomes a valid argument to cache:clean and cache:disable. The schema permits no other elements, which is why there is nowhere here to set a lifetime.

Example · xml
<!-- app/code/Vendor/PriceCache/etc/cache.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Cache/etc/cache.xsd">
    <type name="vendor_price_grid"
          translate="label,description"
          instance="Vendor\PriceCache\Model\Cache\Type\PriceGrid">
        <label>Tier Price Summaries</label>
        <description>Precomputed tier price rollups per product</description>
    </type>
</config>

Storing and reading

The read-through pattern you will write every time: load, return on a hit, compute and save on a miss. The promoted constructor parameters private readonly PriceGrid $cache are shorthand; the longhand is a private readonly PriceGrid $cache; property plus $this->cache = $cache; in the body, and the two produce the same class. Note the 3600 in the fourth argument of save(), which is the only place a lifetime can be set.

Example · php
<?php
// app/code/Vendor/PriceCache/Model/TierSummary.php
declare(strict_types=1);

namespace Vendor\PriceCache\Model;

use Magento\Framework\Serialize\SerializerInterface;
use Vendor\PriceCache\Model\Cache\Type\PriceGrid;

class TierSummary
{
    private const LIFETIME = 3600;

    public function __construct(
        private readonly PriceGrid $cache,
        private readonly SerializerInterface $serializer
    ) {
    }

    public function get(int $productId): array
    {
        $key = 'tier_summary_' . $productId;
        $cached = $this->cache->load($key);

        if ($cached !== false) {
            return (array)$this->serializer->unserialize($cached);
        }

        $summary = $this->calculate($productId);

        $this->cache->save(
            $this->serializer->serialize($summary),
            $key,
            ['cat_p_' . $productId],
            self::LIFETIME
        );

        return $summary;
    }

    private function calculate(int $productId): array
    {
        return ['product_id' => $productId, 'tiers' => 3];
    }
}

// Output of get(42) on a cold cache and on a warm one:
// ['product_id' => 42, 'tiers' => 3]

Clearing just your type

Because the type extends TagScope, clean() with no tags is rewritten internally to clean only entries carrying VENDOR_PRICE_GRID. Disabling it is the fast way to prove a bug is a caching bug: if the symptom survives with the type off, the cache was not the cause.

Example · bash
$ bin/magento cache:clean vendor_price_grid
Cleaned cache types:
vendor_price_grid

$ bin/magento cache:disable vendor_price_grid
Changed cache status:
       vendor_price_grid: 1 -> 0

Discussion

  • Be the first to comment on this lesson.