Cache Reference¶
Using a builtin cache¶
The easiest way to access a cache is to call Civi::cache()
and request a built-in cache. There are two useful built-in caches:
Civi::cache('long')
: This is for things which are more expensive to refresh. By default, it stores data in a SQL table. The data will be long-lived.Civi::cache('short')
: This is for things which are less expensive to refresh. By default, it stores data in a localarray
. The data will be short-lived.
In some environments, the sysadmin configures with a memory-cache service like Redis or Memcached -- in which case both short
and long
will use the memory-cache. The choice between them depeneds on the preferred lifespan in a vanilla/non-optimized environment. To wit:
- If refreshing the data is relatively expensive (comparable to a remote HTTP request), use
long
. It's better to have a SQL-based cache than array-based cache. Sending a SQL query to the cache is preferrable to sending a remote HTTP query.- Example: If a dashlet displays a remote HTTP feed of recent blog posts, it could use
long
cache.
- Example: If a dashlet displays a remote HTTP feed of recent blog posts, it could use
- If refreshing the data is relatively cheap (comparable to a single on-premises SQL request), use
short
. It would be silly to use SQL-based cache - because a cache-hit is no faster than a direct read, and a cache-miss is more expensive than a direct read.- Example: If some data-import code needs to frequently consult the list of
SELECT id, name FROM some_meta_table
, then it could useshort
cache.
- Example: If some data-import code needs to frequently consult the list of
Methods¶
The CRM_Utils_Cache_Interface
class lays out the methods for saving and retrieving cached items.
-
Set a cache value
Civi::cache()->set('mykey', 'myvalue');
-
Get a cached value
Civi::cache()->get('mykey'); // returns the value, or NULL if not set
-
Delete a cached value
Civi::cache()->delete('mykey');
-
Flush the entire cache
Civi::cache()->clear();
PSR-16 Compliance (v5.4+)
In CiviCRM v5.4+, the cache complies with the PHP-FIG standard PSR-16. CRM_Utils_Cache_Interface
extends the simple CacheInterface
, although the implementations differ in a couple small ways:
- The flush function has two names --
clear()
(per PSR-16) andflush()
(which predates PSR-16). These are synonyms. - Cache keys in PSR-16 are prohibited from using certain characters. However, some of these characters were supported in previous versions of CiviCRM's cache interface. To enable a transition, these restrictions are not enforced in a default runtime. However, they are enforced during testing, and they can be enabled in
civicrm.settings.php
by togglingCIVICRM_PSR16_STRICT
.
Example¶
/**
* Finds the magic number, selecting one if necessary.
*
* @return int $magicNumber
* a magic number between 1 and 100
*/
function findMagicNumber() {
$magicNumber = Civi::cache()->get('magicNumber');
if (!$magicNumber) {
$magicNumber = rand(1,100);
Civi::cache()->set('magicNumber', $magicNumber);
}
return $magicNumber;
}
Configuration¶
In a stock configuration, the Civi::cache()
object stores data in a local PHP variable (ArrayCache
). This allows frequent, high-speed I/O, but
it only retains data for the scope of one page-request -- which reduces the potential performance gains.
System administrators may configure the default cache to use a more long-term backend, such as Memcached
or Redis
. For more information about
configuring the default cache driver, see System Administrator Guide => Setup => Caches.
Aliases¶
In reading code, you may find these three notations -- which all refer to the same thing which is essentially a 'short' cache as described below:
Civi::cache()
Civi::cache('default')
CRM_Utils_Cache::singleton()
long and short caches¶
In CiviCRM Codebase there are generally 2 types of caches discussed 'long' and 'short' caches. Both Long and Short caches can be stored in a cache aggregation system such as Memcached or Redis or APC. For most CiviCRM users that do not implement such caching mechanisms, Long caches are stored in the SQL Database in the civicrm_cache
table and short caches are stored in an ArrayCache which is just a PHP array instance.
When calling code such as Civi::cache()->
This resolves to the default cache which is an instance of short cache. This basically around about way is the equivalent of calling CRM_Utils_Cache::create
with the storage type
parameter set to ['*memory*', 'ArrayCache']
where as calling Civi::cache('long')->
or Civi::cache('settings')
or Civi::cache('session')
as some examples of 'long' caches this sets the storage type to be ['*memory*', 'SqlGroup', 'ArrayCache']
.
By default neither short or long caches use the withArray parameter which would allow some PHP Thread optimisation. This may change in the future however at present to utilisation a PHP arrayCache in front of say Redis or Memcached etc, then the cache would have to be defined with withArray => TRUE
or withArray => fast
.
Using a custom cache¶
Generally, it's best to store caches in a memory-backed service like Redis or Memcached. But what happens if the system-configuration doesn't support that? Perhaps you store the cache in a MySQL table? Or a data-file? Or a PHP array?
The answers should not be the same for all data. For example:
- If the cache is tracking metadata derived from
civicrm_option_value
, then you can get the original data pretty quickly (by querying MySQL). Writing the cache to another MySQL table or data-file would serve little benefit. - If the cache is tracking a remote feed (fetched from another continent via HTTPS), then it's much more expensive to get the original data. In absence of Redis/Memcached, you might put the cache in a MySQL table or a data-file.
With a custom cache object, a developer gets the same interface (CRM_Utils_Cache_Interface
/ PSR-16), but they can define different preferences
for how to store the cache-data. In particular, you can define a fallback list. Compare these examples:
['SqlGroup', 'ArrayCache']
means "If MySQL is available, use that. Otherwise, use a local PHP array."['*memory*', 'SqlGroup', 'ArrayCache']
means "If any memory service is available, then use that. Otherwise, if MySQL is available, use that. As a last resort, use a local PHP array."
You can manually instantiate a custom cache object using the factory-function, CRM_Utils_Cache::create()
. This is good for demonstrating the concept and for some edge-cases. However,
in typical usage, it's more common to register a named service.
Example: Manual¶
// Create a cache object
$cache = CRM_Utils_Cache::create([
'type' => ['SqlGroup', 'ArrayCache'],
'name' => 'HelloWorld',
]);
A few things to notice here:
- The
type
parameter is an array of preferred storage systems. - The
name
will be passed down to the storage system -- ensuring that different caches are stored separately.- Ex: In
Memcached
/Redis
, thename
becomes part of the cache-key. - Ex: In
SqlGroup
, thename
corresponds to the fieldcivicrm_cache.group_name
.
- Ex: In
- There is an optional
withArray
parameter with the following acceptable options.- FALSE (default): Reads+writes go directly to the underlying cache
- TRUE: There's an extra array-based cache-tier in front of the underlying cache. It uses
CRM_Utils_Cache_ArrayDecorator
.- This variant is more correct/compliant with PSR-16 in that TTL should be consistent between the front-tier cache and the underlying-cache. It requires a more verbose storage-format, which slightly reduces performance. (Not measurable for 1-4 reads; would be measureable for 1000 reads.)
- fast: There's an extra array-based cache-tier in front of the underlying cache. It uses
CRM_Utils_Cache_FastArrayDecorator
.- This variant is more performant and uses a cleaner/simpler storage-format; however, you're more likely to get stale reads from the front-tier cache. It's not much of practical drawback in typical usage (where the PHP interpreter only runs for <1s; stale info in the front-cache doesn't hang around long anyway). However, the (in)correctness could be an issue in long-run jobs.
Once you have the $cache
object, it supports all the methods of CRM_Utils_Cache_Interface
and PSR-16.
// Use the cache object
$value = $cache->get('name');
if ($value === NULL) {
$cache->set('name', 'Alice');
}
// Flush the contents of the cache
$cache->flush();
Example: Named service¶
First, we define the service in Civi\Core\Container
or hook_civicrm_container
:
use Symfony\Component\DependencyInjection\Definition;
$container->setDefinition("cache.hello", new Definition(
'CRM_Utils_Cache_Interface',
[[
'type' => ['*memory*', 'SqlGroup', 'ArrayCache'],
'name' => 'HelloWorld',
]]
))->setFactory('CRM_Utils_Cache::create')->setPublic(TRUE);
As before, notice that:
- The
type
parameter is an array of preferred storage systems. It will choose the first valid driver. - The
name
will be passed down to the storage system. - The service is an instance of
CRM_Utils_Cache_Interface
(PSR-16). - There is an optional
withArray
parameter as described above as well
Once the service is declared, we can get a reference to the cache in several ways:
- Lookup the service with
Civi::cache('hello')
- Lookup the service with
Civi::service('cache.hello')
- Inject the service using the container's dependency-injection
For example, we could use Civi::cache('hello')
as follows:
// Use the cache object
$value = Civi::cache('hello')->get('name');
if ($value === NULL) {
Civi::cache('hello')->set('name', 'Alice');
}
// Flush the contents of the cache
Civi::cache('hello')->flush();