From 8982d978203f168ed592e11a1dd5827dfaf1749f Mon Sep 17 00:00:00 2001 From: Tim Otten <totten@civicrm.org> Date: Mon, 13 Aug 2018 19:43:24 -0700 Subject: [PATCH] cache.md - Add section on custom cache objects --- docs/framework/cache.md | 90 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/docs/framework/cache.md b/docs/framework/cache.md index 7b518cdd..ee588f98 100644 --- a/docs/framework/cache.md +++ b/docs/framework/cache.md @@ -66,3 +66,93 @@ In reading code, you may find these three notations -- which all refer to the sa * `Civi::cache()` * `Civi::cache('default')` * `CRM_Utils_Cache::singleton()` + +## Using a custom cache {:#custom} + +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 arrray? + +The answers are not 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 a data-file would just slow things down. +* 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`), 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 + +```php +// 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`, the `name` becomes part of the cache-key. + * Ex: In `SqlGroup`, the `name` corresponds to the field `civicrm_cache.group_name`. + +Once you have the `$cache` object, it supports all the methods of `CRM_Utils_Cache_Interface`. + +```php +// 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`: + +```php +$container->setDefinition("cache.hello", new Definition( + 'CRM_Utils_Cache_Interface', + [[ + 'type' => ['*memory*', 'SqlGroup', 'ArrayCache'], + 'name' => 'HelloWorld', + ]] +))->setFactory('CRM_Utils_Cache::create'); +``` + +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`. + +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: + +```php +// 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(); +``` -- GitLab