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