PHPackages                             silverstripe-terraformers/keys-for-cache - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Caching](/categories/caching)
4. /
5. silverstripe-terraformers/keys-for-cache

ActiveSilverstripe-vendormodule[Caching](/categories/caching)

silverstripe-terraformers/keys-for-cache
========================================

Silverstripe cache key management

3.0.0(9mo ago)1726.6k—2.5%4BSD-3-ClausePHPPHP ^8.1CI passing

Since Sep 23Pushed 9mo ago10 watchersCompare

[ Source](https://github.com/silverstripe-terraformers/keys-for-cache)[ Packagist](https://packagist.org/packages/silverstripe-terraformers/keys-for-cache)[ RSS](/packages/silverstripe-terraformers-keys-for-cache/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (5)Versions (28)Used By (0)

Keys for Cache (KFC)
====================

[](#keys-for-cache-kfc)

[![Build Status](https://github.com/silverstripe-terraformers/keys-for-cache/actions/workflows/main.yml/badge.svg)](https://github.com/silverstripe-terraformers/keys-for-cache/actions/workflows/main.yml/badge.svg)[![codecov](https://camo.githubusercontent.com/a26230f79f23b124bedae2314b4df1eadebb47fb1732fa1722197c6a2342c614/68747470733a2f2f636f6465636f762e696f2f67682f73696c7665727374726970652d7465727261666f726d6572732f6b6579732d666f722d63616368652f6272616e63682f6d61737465722f67726170682f62616467652e737667)](https://codecov.io/gh/silverstripe-terraformers/keys-for-cache)

This module helps you create singular cache keys which can be used as part of any cache strategy, but probably most commonly with [Silverstripe's Partial Caching](https://docs.silverstripe.org/en/4/developer_guides/performance/partial_caching/)feature.

**The overall aim of this module is twofold:**

1. Make it easier for developers to create cache keys for our `DataObjects` (especially for those that have complex dependencies).
2. Increase the performance of our applications by reducing the number of, and the complexity of the cache keys that we calculate at the time of an end user's request.

**How will we achieve these goals?**
We will use and enhance existing config paradigms that Silverstripe developers are already familiar with.

We will move the cost of calculating cache keys to when the changes are made to our `DataObjects`, rather than at the time of an end user's request.

**What this module is not**:
This module is not here to provide a caching method, it is here to provide you with **keys** that you can use with your preferred caching method (EG: partial caching).

- [Installation](#installation)
- [Why cache keys are difficult](#why-cache-keys-are-difficult)
- [How we aim to solve these difficulties](#how-we-aim-to-solve-these-difficulties)
- [Setup and configuration](#setup-and-configuration)
    - [Has cache key](#has-cache-key)
    - [Cares](#cares)
    - [Touches](#touches)
    - [Global cares](#global-cares)
    - [Usage and Examples](docs/en/examples.md)
- [Performance impact/considerations](#performance-impactconsiderations)
    - [Queued jobs](#queued-jobs)
- [Case Studies](docs/en/case-studies.md)
- [Fluent support](docs/en/fluent.md)
- [Unit testing](docs/en/unit-testing.md)
- [License](license.md)
- [Maintainers](#maintainers)
- [Development and contribution](#development-and-contribution)

Installation
------------

[](#installation)

```
composer require silverstripe-terraformers/keys-for-cache

```

Requirements
------------

[](#requirements)

- PHP `^8.1`
- Silverstripe Framework `^5`

Support for Silverstripe 4 is provided though our `^1` tagged releases.

Why cache keys are difficult
----------------------------

[](#why-cache-keys-are-difficult)

TL;DR: Cache keys need to be updated any time a related piece of information changes, and we often have structures of content (EG: Carousel blocks) that contain many levels of nested `DataObjects`.

The goal of any cache key is to have as low a cost as possible to calculate (as this must happen with every request), but also for it to invalidate at the appropriate times (IE: when a piece of relevant content has changed).

Consider the following:

- We have a `Page` with the Elemental module applied.
- One of the blocks available is a Carousel block. This block itself contains Carousel Items, and each Item contains an Image.
- When a change is made to the block itself, to one of its Items, or to any of the Images assigned to its Items, the cache key should be invalidated.

A cache key for a single Carousel block might look something like:

```
public function getCacheKey(): string
{
    $parts = [
        // Parts related to the block itself
        static::class,
        $this->ID,
        $this->LastEdited,
        // Parts related to the Items within the block
        $this->Items()->max('LastEdited'),
        $this->Items()->count(),
        // Parts related to the Images assigned to our Items
        Image::get()->filter('ID', $this->Items()->column('ImageID'))->max('LastEdited'),
        Image::get()->filter('ID', $this->Items()->column('ImageID'))->count(),
    ];

    return implode('-', $parts);
}
```

This can be optimised a bit, but the point is: It requires multiple queries each time it is performed, and it requires a decent chunk of cognitive load to understand (and to come up with in the first place).

In short, we want to achieve the same outcome as the example above, but with a simple configuration, like this:

```
App\Blocks\CarouselBlock:
    has_cache_key: true
    cares:
        - Items

App\Blocks\CarouselItem:
    cares:
        - Image
```

Setup and configuration
-----------------------

[](#setup-and-configuration)

**Preamble:** When we talk about "changes to records", this includes all C.R.U.D. actions.

**Relationship config:** In order for this module to function, we often need to understand more about the relationships that your `DataObjets` have than perhaps Silverstripe ORM does. [More on this here](docs/en/relationship-config.md).

### Has cache key

[](#has-cache-key)

First, you need to tell us which DataObjects you would like cache keys to be generated for. For example, we might like a key for all pages and all Elements:

```
Page:
    has_cache_key: true

DNADesign\Elemental\Models\BaseElement:
    has_cache_key: true
```

By adding this configuration, you will have access to the `getCacheKey()` method on your `DataObject`, and the `$CacheKey` variable in your template when you have that `DataObject` in scope.

Next, in order to make sure that your cache keys invalidate when related `DataObjects` are updated, you need to define any dependencies that your model might have.

There are three important configurations to be aware of:

- [Cares](#cares)
- [Touches](#touches)
- [Global cares](#global-cares)

### Cares

[](#cares)

This configuration determines how `$this` `DataObject` will be affected when **other** (related) `DataObjects` are manipulated.

For example: We have requested that all Elements have cache keys. If you have a `CarouselBlock` that contains `CarouselItems`, then you will want to make sure that the cache key for the `CarouselBlock` is invalidated any time a `CourselItem` is updated.

You can do that by telling us that your `CarouselBlock` `cares` about its `Items`.

```
App\Blocks\CarouselBlock:
    cares:
        - Items
```

Or in your class:

```
class CarouselBlock extends BaseElement
{
    private static array $has_many = [
        'Items' => CarouselItem::class,
    ];

    // $owns is optional, but quite common in this use case. I've added it here simply to illustrate how $cares
    // follows the same paradigm
    private static array $owns = [
        'Items',
    ];

    private static array $cares = [
        'Items',
    ];
}
```

Take the original example where we also wanted our `CarouselItem` to include changes to Images as part of its cache key. We could now also add a `cares` config to our `CarouselItem`:

```
App\Blocks\CarouselItem:
    cares:
        - Image
```

Or in your class like so:

```
class CarouselItem extends DataObject
{
    private static array $has_one = [
        'Image' => Image::class,
    ];

    // $owns is optional, but quite common in this use case. I've added it here simply to illustrate how $cares
    // follows the same paradigm
    private static array $owns = [
        'Image',
    ];

    private static array $cares = [
        'Image',
    ];
}
```

Now whenever the linked `Image` is updated, it will also update the `CarouselItem`, and in turn the `CarouselItem` will update the linked `Carousel`. Taking this a step further all the way back to `Page`, we could also add the following:

```
# Our BlockPage cares about changes to its ElementalArea
App\Elemental\BlockPage:
    cares:
        - ElementalArea

# Our ElementalAreas care about any changes made to its Elements
DNADesign\Elemental\Models\ElementalArea:
    cares:
        Elements: BaseElement::class
```

Our `BlockPage` now `cares` about our `ElementalArea`, and our `ElementalArea` now cares about all of its blocks/elements. This now means that any time a change is made to any of our blocks (so long as we have configured them similarly to how we have shown with the `Carousel`), we will get a new cache key value on our `Page`.

Alternatively, you could achieve the same outcome by using [Touches](#touches).

**Important note:** Your `DataObject` does not need to `has_cache_key` in order for it to `care` or `touch` other `DataObjects`. In fact, we very much rely on you providing us with the full relationship tree through `cares/touches`. We will only generate the cache key for `DataObjects` that `has_cache_key`, but we will continue to follow the paths you create until we run out of them.

**Important note:** Definitely consider the performance consideration of invalidating your `Page` cache any time an element is updated. It has been added above purely as an example of what it technically possible; it has not been added as a recommendation.

### Touches

[](#touches)

This configuration determines how `$this` `DataObject` will affect others when it is updated. EG: when `$this` `DataObject` is updated, it should "touch" some other `DataObjects` so that they too have their cache keys invalidated.

Using the example from above, if you have a `CarouselBlock` with `CarouselItems` then you could alternatively configure it like so (where the `key` is the field relationship name, and the value is the `class` that it relates to):

```
App\Blocks\CarouselItem:
    touches:
        - Parent
```

Or in your class like so:

```
class CarouselItem extends DataObject
{
    private static array $has_one = [
        'Parent' => CarouselBlock::class,
    ];

    private static array $touches = [
        'Parent',
    ];
}
```

This would mean that any time the `CarouselItem` is updated it would also update the cache key of the parent `CarouselBlock`.

Alternatively, you could achieve the same outcome by using [Cares](#cares).

**Important note:** Your `DataObject` does not need to `has_cache_key` in order for it to `care` or `touch` other `DataObjects`. In fact, we very much rely on you providing us with the full relationship tree through `cares/touches`. We will only generate the cache key for `DataObjects` that `has_cache_key`, but we will continue to follow the paths you create until we run out of them.

### Global cares

[](#global-cares)

You might run into situations where you need your cache key to be updated when *any* DataObject of a certain class is changed. For example, we could have a "Recent updates block", which lists out the pages that have been most recently updated. For this, we would want to make it so that when *any* page is updated, it will also invalidate the cache keys for any DataObject that cares about global changes to our pages.

```
App\Blocks\RecentUpdates:
    global_cares:
        - SilverStripe\CMS\Model\SiteTree
```

**Important note:** These global updates won't use the touches/cares when they occur. For example, if `Blocks\RecentUpdates` had a `touches` of `Link:  SilverStripe\CMS\Model\SiteTree`, The site tree wouldn't be updated. This is a mechanism of global updates to ensure we don't run into performance issues

### Usage and examples

[](#usage-and-examples)

See: [Usage and Examples](docs/en/examples.md)

Performance impact/considerations
---------------------------------

[](#performance-impactconsiderations)

This will increase the queries to the database when `DataObjects` are updated, but so far there has been no noticeable increase to (eg) the publishing time of pages within the CMS, nor has there been any noticeable increase in processing time of cron jobs related to the manipulation of `DataObjects`.

**That said:**

- You should still be aware of what `cares` and `touches` configuration you enabled.
- If you start to notice performance issues with (eg) Publishing a page, then you might need to reconsider the scope of relationships that you `cares` or `touches` as part of your page and related `DataObjects` (EG: blocks).

### Queued jobs

[](#queued-jobs)

If you want to prevent content authors from getting slightly slower responses when editing in the CMS, you can queue a job to generate the cache updates by injecting over `CacheKeyExtension` and updating `triggerEvent` to create a job then call `CacheRelationService::singleton()->processChange($this->DataObject)` in the job.

Case studies
------------

[](#case-studies)

See: [Case studies](docs/en/case-studies.md)

Fluent support
--------------

[](#fluent-support)

See: [Fluent support](docs/en/fluent.md)

Unit testing
------------

[](#unit-testing)

See: [Unit testing](docs/en/unit-testing.md)

License
-------

[](#license)

See [License](license.md)

Maintainers
-----------

[](#maintainers)

- Adrian Humphreys
- Chris Penny
- Adrian Jimson

Development and contribution
----------------------------

[](#development-and-contribution)

If you would like to make contributions to the module please ensure you raise a pull request and discuss with the module maintainers.

###  Health Score

49

—

FairBetter than 95% of packages

Maintenance57

Moderate activity, may be stable

Popularity37

Limited adoption so far

Community21

Small or concentrated contributor base

Maturity69

Established project with proven stability

 Bus Factor1

Top contributor holds 88.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~54 days

Recently: every ~191 days

Total

27

Last Release

285d ago

Major Versions

0.0.13 → 1.0.02022-07-07

1.1.0 → 2.0.02023-02-26

1.x-dev → 2.x-dev2024-06-19

2.x-dev → 3.0.02025-08-06

PHP version history (3 changes)0.0.1PHP &gt;=7.4

0.0.11PHP ^7.4 || ^8.0

2.0.0PHP ^8.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/505788?v=4)[Chris Penny](/maintainers/chrispenny)[@chrispenny](https://github.com/chrispenny)

![](https://avatars.githubusercontent.com/u/16602727?v=4)[mfendek](/maintainers/mfendek)[@mfendek](https://github.com/mfendek)

![](https://avatars.githubusercontent.com/u/855269?v=4)[Hayden Shaw](/maintainers/thats4shaw)[@thats4shaw](https://github.com/thats4shaw)

![](https://avatars.githubusercontent.com/u/948122?v=4)[Bernard Hamlin](/maintainers/blueo)[@blueo](https://github.com/blueo)

![](https://avatars.githubusercontent.com/u/10803137?v=4)[Adrian Humphreys](/maintainers/adrhumphreys)[@adrhumphreys](https://github.com/adrhumphreys)

---

Top Contributors

[![chrispenny](https://avatars.githubusercontent.com/u/505788?v=4)](https://github.com/chrispenny "chrispenny (63 commits)")[![TheSceneman](https://avatars.githubusercontent.com/u/88803020?v=4)](https://github.com/TheSceneman "TheSceneman (4 commits)")[![adrhumphreys](https://avatars.githubusercontent.com/u/10803137?v=4)](https://github.com/adrhumphreys "adrhumphreys (2 commits)")[![blueo](https://avatars.githubusercontent.com/u/948122?v=4)](https://github.com/blueo "blueo (1 commits)")[![monkeyfeet](https://avatars.githubusercontent.com/u/4185129?v=4)](https://github.com/monkeyfeet "monkeyfeet (1 commits)")

---

Tags

hacktoberfestsilverstripecachekeys

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/silverstripe-terraformers-keys-for-cache/health.svg)

```
[![Health](https://phpackages.com/badges/silverstripe-terraformers-keys-for-cache/health.svg)](https://phpackages.com/packages/silverstripe-terraformers-keys-for-cache)
```

###  Alternatives

[silverstripe/staticpublishqueue

Static publishing queue to create static versions of pages for enhanced performance and security

45135.4k4](/packages/silverstripe-staticpublishqueue)[pstaender/silverstripe-redis-cache

Enables Redis cache for SilverStripe

1199.1k](/packages/pstaender-silverstripe-redis-cache)[tractorcow/silverstripe-dynamiccache

FORK OF Silverstripe module for simple on the fly caching of dynamic content

3916.0k2](/packages/tractorcow-silverstripe-dynamiccache)[steadlane/silverstripe-cloudflare

This module aims to relieve the stress of using Cloudflare caching with any SilverStripe project. Adds extension hooks that clears Cloudflare's cache for a specific page when that page is published or unpublished.

243.7k](/packages/steadlane-silverstripe-cloudflare)[silverstripe-australia/simplecache

Simple(r) caching abstraction layer, with a static publisher that will use that cache layer for storage

146.8k](/packages/silverstripe-australia-simplecache)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
