germania-kg/cachecallable

Callable convenience wrapper around PSR-6 Cache Item Pools: Seamlessly create, return, and store your data.

2.1.3 2022-05-05 14:34 UTC

README

Packagist PHP version Build Status Scrutinizer Code Quality Code Coverage Build Status

Callable convenience wrapper around PSR-6 Cache Item Pools: Seamlessly creates, returns, and stores your data.

Caching business is pretty much always similar and can be outlined like this:

  1. Is caching enabled at all? If not, delete any according older entry first.
    Create and return fresh content anyway, ending up here.
  2. Does a given item exist? If so, return item content;
    Otherwise, create, store and return content.

The CacheCallable class reduces these steps to a handy and customizable Callable.

Installation with Composer

$ composer require germania-kg/cachecallable

Example

Although this example uses phpfastcache, you should be able to pass in any Cache Item Pool. Use your favourite PSR-3 Logger; this example will use the well-known Monolog.

<?php
use phpFastCache\CacheManager;
use Monolog\Logger;
use Germania\Cache\CacheCallable;

// Setup dependencies
$cacheItemPool = CacheManager::getInstance('files', [ options ]);
$lifetime      = 3600;
$monolog       = new Logger( "My App" );
$content_creator = function( $keyword ) {
	return "Cache keyword: " . $keyword;
};


//
// Setup Cache wrapper
// 
$wrapped_cache = new CacheCallable(
	$cacheItemPool,
	$lifetime,
	$creator,
	$monolog // optional
);

// Identifying key. Example for a web page:
$keyword = sha1($_SERVER['REQUEST_URI']);
echo $wrapped_cache( $keyword );

A word on cache keys

According to the PSR-6 specs, cache keys should be limited to A-Z, a-z, 0-9, _, and . to ensure maximum compatibilty. So if you pass in a PSR-6 Adapter from Symfony Cache component, class CacheCallable internally converts the given keys to a MD5 representation.

In case you'd like to provide a custom cache key creation, you may use the setCacheKeyCreator method whoch accepts any callable:

$wrapped_cache->setCacheKeyCreator( function($raw) { return sha1($raw); } );

PHP-FIG: PSR-6: Caching Interface

Implementing libraries MUST support keys consisting of the characters A-Z, a-z, 0-9, _, and . in any order in UTF-8 encoding and a length of up to 64 characters. Implementing libraries MAY support additional characters and encodings or longer lengths, but must support at least that minimum.

Symfony docs: “Cache Item Keys and Values”

The key of a cache item […] should only contain letters (A-Z, a-z), numbers (0-9) and the _ and . symbols.

The Cache lifetime

Think of a webpage that turns out to be not cached during script runtime — after we set up the Cache wrapper. For this reason, the Cache wrapper constructor also accepts a LifeTimeInterface implementation with a getValue method:

<?php
namespace Germania\Cache;

interface LifeTimeInterface {
	// Return seconds to expiration
	public function getValue();
}

The LifeTime class is a simple implementation of this interface. It additionally enables you to change the lifetime value during runtime. Since we passed it to our Cache constructor by reference, the Cache wrapper can decide after content creation wether to cache or not.

Create a Lifetime object

<?php
use Germania\Cache\CacheCallable;
use Germania\Cache\LifeTime;

// Setup LifeTime object
$lifetime_object = new LifeTime( 3600 );

// Use Factory method:
$lifetime_object = LifeTime::create( 3600 );

// Create from Lifetime instance
$another_lifetime = new LifeTime( $lifetime_object );
$another_lifetime = LifeTime::create( $lifetime_object );

Usage with CacheCallable

<?php
use Germania\Cache\CacheCallable;
use Germania\Cache\LifeTime;

// Taken from example above
$wrapped_cache = new CacheCallable(
	$cacheItemPool,
	$lifetime_object,
	$creator
);

Your Logger will now output something like this:

MyLogger DEBUG Lifetime after content creation: 0
MyLogger NOTICE DO NOT store in cache

How to change lifetime during script runtime

After instantation, you may use the setValue method:

<?php
namespace Germania\Cache;

interface LifeTimeInterface {
	// Return seconds to expiration
	public function getValue();
}

The LifeTime class is a simple implementation of this interface. It additionally enables you to change the lifetime value during runtime. Since we passed it to our Cache constructor by reference, the Cache wrapper can decide after content creation wether to cache or not.

Create a Lifetime object:

<?php
use Germania\Cache\CacheCallable;
use Germania\Cache\LifeTime;

// Setup LifeTime object
$lifetime_object = new LifeTime( 3600 );

// Use Factory method:
$lifetime_object = LifeTime::create( 3600 );

// Create from Lifetime instance
$another_lifetime = new LifeTime( $lifetime_object );
$another_lifetime = LifeTime::create( $lifetime_object );

Set time value after instantation

// Change LifeTime value during runtime, 
// e.g. in router or controller
$lifetime_object->setValue( 0 );

Usage with CacheCallable

<?php
use Germania\Cache\CacheCallable;
use Germania\Cache\LifeTime;

// Taken from example above
$wrapped_cache = new CacheCallable(
	$cacheItemPool,
	$lifetime_object,
	$creator
);

Your Logger will now output something like this:

MyLogger DEBUG Lifetime after content creation: 0
MyLogger NOTICE DO NOT store in cache

How to override content creation

If you prefer singleton services, you may invoke the CacheCallable with a custom content creator parameter to override the default one:

// Default content creator
$default_creator = function($file) {
	return json_decode( file_get_contents($file) );
};

// Setup Service
$wrapped_cache = new CacheCallable(
    $cacheItemPool,
    $lifetime_object,
    $default_creator
);

// Override content creation 
$config = $wrapped_cache("config.json", function( $file ) {
	return array('foo' => 'bar');
};

Issues

The PSR-6 Caching Interface mock in CacheCallableTest could need an overhaul. Discuss on #issue 3

Development

$ git clone https://github.com/GermaniaKG/CacheCallable.git
$ cd CacheCallable
$ composer install

Unit tests

Either copy phpunit.xml.dist to phpunit.xml and adapt to your needs, or leave as is. Run PhpUnit test or composer scripts like this:

$ composer test
# or
$ vendor/bin/phpunit

Useful Links