Caching function values and refreshing by need for efficiency

Hi! Long time user of Kirby – and this forum – here. Thank you for Kirby; it’s awesome! :sparkles:

I’m considering building a local caching system for speeding up functions that either take a while to compute or that are called very often.

Am I on the right track here, or perhaps there are better or more elegant solutions?

Let’s say that I have a function like this (in a Page object):

public function complicatedCalculation()
{
    // Return value.
    return “Something that takes some time to compute.”
}

I would like to (1) use a cached value if it exists (and if it does not exist, create it), and (2) manually refresh the cached value, via a hook, only when necessary.

So far, this is what I got.

In config.php, I have added this:

'cache' => [
    'cachedvalues' => true
]

Here is what the above function could look like with caching. The idea is to use the Page object and the name of the function as a unique key for the cached value:

public function complicatedCalculation($useCache = true)
{
    // Use cache if possible.
    if ($useCache) return getCachedValue($this, 'complicatedCalculation');

    // Return value.
    return “Something that takes some time to compute.”
}

Here is the main function, getCachedValue, to achieve this:

function getCachedValue($page, $key, $refreshCache = false) {

    // Get cache.
    $cache = kirby()->cache('cachedvalues');

    // Get cache key.
    $cacheKey = $page->id() . '/' . $key;

    // Get cached value, if it exists.
    $cachedValue = $cache->get($cacheKey);

    // Update the cached value if the value is not set or we are manually refreshing it.
    if ($cachedValue === null || $refreshCache) {

        // Get the value by calling the function named $key for the $page object.
        $cachedValue = $page->$key(useCache: false);

        // Set cache.
        $cache->set($cacheKey, $cachedValue);
    }

    // Return cached value.
    return $cachedValue;
}

Then, in the appropriate hooks, I can manually update the values exactly when I need to. For example like this:

'page.update:after' => function ($newPage, $oldPage) {
    getCachedValue($newPage, 'complicatedCalculation', refreshCache: true);
}

Am I doing this right, or am I confused? Are there obvious improvements or things I’m missing? So far, it seems to work great, but I suspect that there are better ways to achieve what I want. Suggestions or ideas?

1 Like

I’d include an option to ignore the cache in debug mode, without having to set a function parameter.

This should probably have some conditions when the cache should be flushed/renewed, otherwise this method is called on every update for every page.

1 Like

Thank you, I agree, and I have added a bunch of logic to determine when to update the cache.

I take it that the overall setup is not too bad. :slight_smile:

One idea: Perhaps it is better to use the UUIDs somehow, as the basis of the caching key?

i created my lapse plugin for similar reasons.

it uses a callback to delay the execution of the time consuming calculation (much like your page method), can track modified timestamps of objects automatically (via the key and thus update the cache on changed objects), serializes kirby objects to primitive types.

a complex example would look like this:

If this page, any of its sub pages images, the site object or any object in that pages collection changes then refresh the cache. below is the call to erase that cache value manually.

$data = lapse(
    ['myhash', $page, $page->children()->images(), $collection, $site], // will create key from array of objects
    function () use ($site, $page, $collection) {
        return [
            // will not break serialization => automatically store ->value() of fields
            'author' => $site->author(),
            'title' => $page->title(),
            'hero' => $page->children()->images()->first()->srcset()->html(),
            'count' => $collection->count(),
        ];
    }
);

// remove by dynamic key
$wasRemoved = \Bnomei\Lapse::rm(
    ['myhash', $page, $page->children()->images(), $collection, $site]
);