From 5a302bbc2f3bd75b28401667f48966d65a4a0b36 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 21 Jan 2019 03:06:50 -0800 Subject: [PATCH] Allow rerouting CRM_Core_BAO_Cache::{set,get}Item(s) to PSR-16 drivers Before ---------------------------------------- * Requests for `CRM_Core_BAO_Cache` (`setItem($data,$group,$path)`, `getItem($group,$path)`, `getItems($group)`, `deleteGroup($group,$path)`) are *always* served by two tiers: (1) an in-memory array (`static::$cache`) and (2) an SQL table. After ---------------------------------------- * There is a config option `define('CIVICRM_BAO_CACHE_ADAPTER', 'CRM_Core_BAO_Cache_Psr16');`. * When disabled (default), `CRM_Core_BAO_Cache` continues using the old code. * When enabled, `CRM_Core_BAO_Cache` changes behavior. Each `$group` is mapped to a PSR-16 object. * The class/implementation for each `$group` depends on the configuration: * In a typical (non-Redis/non-Memcache) deployment, the implementation is `CRM_Utils_Cache_SqlGroup`, which has the same 2-tier structure (in-memory+SQL). * In Redis/Memcache deployment, the implementation combines `FastArrayDecorator` with `CRM_Utils_Cache_Redis` or `CRM_Utils_Cache_Memcache`. This gives a similar 2-tier structure (e.g. in-memory+Redis). --- CRM/Core/BAO/Cache.php | 23 +- CRM/Core/BAO/Cache/Psr16.php | 201 ++++++++++++++++++ CRM/Core/Config.php | 4 + .../CRM/common/civicrm.settings.php.template | 6 + 4 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 CRM/Core/BAO/Cache/Psr16.php diff --git a/CRM/Core/BAO/Cache.php b/CRM/Core/BAO/Cache.php index 8b32855e4e..43530d2f70 100644 --- a/CRM/Core/BAO/Cache.php +++ b/CRM/Core/BAO/Cache.php @@ -66,6 +66,10 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache { * The data if present in cache, else null */ public static function &getItem($group, $path, $componentID = NULL) { + if ($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) { + return $adapter::getItem($group, $path, $componentID); + } + if (self::$_cache === NULL) { self::$_cache = array(); } @@ -103,6 +107,10 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache { * The data if present in cache, else null */ public static function &getItems($group, $componentID = NULL) { + if ($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) { + return $adapter::getItems($group, $componentID); + } + if (self::$_cache === NULL) { self::$_cache = array(); } @@ -144,6 +152,10 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache { * The optional component ID (so componenets can share the same name space). */ public static function setItem(&$data, $group, $path, $componentID = NULL) { + if ($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) { + return $adapter::setItem($data, $group, $path, $componentID); + } + if (self::$_cache === NULL) { self::$_cache = array(); } @@ -211,9 +223,14 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache { * @param bool $clearAll clear all caches */ public static function deleteGroup($group = NULL, $path = NULL, $clearAll = TRUE) { - $table = self::getTableName(); - $where = self::whereCache($group, $path, NULL); - CRM_Core_DAO::executeQuery("DELETE FROM $table WHERE $where"); + if ($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) { + return $adapter::deleteGroup($group, $path); + } + else { + $table = self::getTableName(); + $where = self::whereCache($group, $path, NULL); + CRM_Core_DAO::executeQuery("DELETE FROM $table WHERE $where"); + } if ($clearAll) { // also reset ACL Cache diff --git a/CRM/Core/BAO/Cache/Psr16.php b/CRM/Core/BAO/Cache/Psr16.php new file mode 100644 index 0000000000..852a4a3c25 --- /dev/null +++ b/CRM/Core/BAO/Cache/Psr16.php @@ -0,0 +1,201 @@ +warning('Unrecognized BAO cache group ({group}). This should work generally, but data may not be flushed in some edge-cases. Consider migrating explicitly to PSR-16.', [ + 'group' => $group, + ]); + } + + $cache = CRM_Utils_Cache::create([ + 'name' => "bao_$group", + 'type' => array('*memory*', 'SqlGroup', 'ArrayCache'), + // We're replacing CRM_Core_BAO_Cache, which traditionally used a front-cache + // that was not aware of TTLs. So it seems more consistent/performant to + // use 'fast' here. + 'withArray' => 'fast', + ]); + Civi::$statics[__CLASS__][$group] = $cache; + } + return Civi::$statics[__CLASS__][$group]; + } + + /** + * Retrieve an item from the DB cache. + * + * @param string $group + * (required) The group name of the item. + * @param string $path + * (required) The path under which this item is stored. + * @param int $componentID + * The optional component ID (so componenets can share the same name space). + * + * @return object + * The data if present in cache, else null + */ + public static function &getItem($group, $path, $componentID = NULL) { + // TODO: Generate a general deprecation notice. + if ($componentID) { + Civi::log() + ->warning('getItem({group},{path},...) uses unsupported componentID. Consider migrating explicitly to PSR-16.', [ + 'group' => $group, + 'path' => $path, + ]); + } + $value = self::getGroup($group)->get(CRM_Core_BAO_Cache::cleanKey($path)); + return $value; + } + + /** + * Retrieve all items in a group. + * + * @param string $group + * (required) The group name of the item. + * @param int $componentID + * The optional component ID (so componenets can share the same name space). + * + * @throws CRM_Core_Exception + */ + public static function &getItems($group, $componentID = NULL) { + // Based on grepping universe, this function is not currently used. + // Moreover, it's hard to implement in PSR-16. (We'd have to extend the + // interface.) Let's wait and see if anyone actually needs this... + throw new \CRM_Core_Exception('Not implemented: CRM_Core_BAO_Cache_Psr16::getItems'); + } + + /** + * Store an item in the DB cache. + * + * @param object $data + * (required) A reference to the data that will be serialized and stored. + * @param string $group + * (required) The group name of the item. + * @param string $path + * (required) The path under which this item is stored. + * @param int $componentID + * The optional component ID (so componenets can share the same name space). + */ + public static function setItem(&$data, $group, $path, $componentID = NULL) { + // TODO: Generate a general deprecation notice. + + if ($componentID) { + Civi::log() + ->warning('setItem({group},{path},...) uses unsupported componentID. Consider migrating explicitly to PSR-16.', [ + 'group' => $group, + 'path' => $path, + ]); + } + self::getGroup($group) + ->set(CRM_Core_BAO_Cache::cleanKey($path), $data, self::TTL); + } + + /** + * Delete all the cache elements that belong to a group OR delete the entire cache if group is not specified. + * + * @param string $group + * The group name of the entries to be deleted. + * @param string $path + * Path of the item that needs to be deleted. + */ + public static function deleteGroup($group = NULL, $path = NULL) { + // FIXME: Generate a general deprecation notice. + + if ($path) { + self::getGroup($group)->delete(CRM_Core_BAO_Cache::cleanKey($path)); + } + else { + self::getGroup($group)->clear(); + } + } + + /** + * Cleanup any caches that we've mapped. + * + * Traditional SQL-backed caches are cleared as a matter of course during a + * system flush (by way of "TRUNCATE TABLE civicrm_cache"). This provides + * a spot where the adapter can + */ + public static function clearDBCache() { + foreach (self::getLegacyGroups() as $groupName) { + $group = self::getGroup($groupName); + $group->clear(); + } + } + + /** + * Get a list of known cache-groups + * + * @return array + */ + public static function getLegacyGroups() { + return [ + // Core + 'CiviCRM Search PrevNextCache', + 'contact fields', + 'navigation', + 'contact groups', + 'custom data', + + // Universe + 'dashboard', // be.chiro.civi.atomfeeds + 'lineitem-editor', // biz.jmaconsulting.lineitemedit + 'HRCore_Info', // civihr/uk.co.compucorp.civicrm.hrcore + 'CiviCRM setting Spec', // nz.co.fuzion.entitysetting + 'descendant groups for an org', // org.civicrm.multisite + ]; + } + +} diff --git a/CRM/Core/Config.php b/CRM/Core/Config.php index 836c2c35a7..2e5c7307da 100644 --- a/CRM/Core/Config.php +++ b/CRM/Core/Config.php @@ -362,6 +362,10 @@ class CRM_Core_Config extends CRM_Core_Config_MagicMerge { CRM_Core_DAO::executeQuery($query); } + if ($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) { + return $adapter::clearDBCache(); + } + // also delete all the import and export temp tables self::clearTempTables(); } diff --git a/templates/CRM/common/civicrm.settings.php.template b/templates/CRM/common/civicrm.settings.php.template index 34aaafe59d..0600dc6e33 100644 --- a/templates/CRM/common/civicrm.settings.php.template +++ b/templates/CRM/common/civicrm.settings.php.template @@ -457,6 +457,12 @@ define('CIVICRM_DEADLOCK_RETRIES', 3); // define('CIVICRM_MYSQL_STRICT', TRUE ); // } +/** + * Specify whether the CRM_Core_BAO_Cache should use the legacy + * direct-to-SQL-mode or the interim PSR-16 adapter. + */ +// define('CIVICRM_BAO_CACHE_ADAPTER', 'CRM_Core_BAO_Cache_Psr16'); + if (CIVICRM_UF === 'UnitTests') { if (!defined('CIVICRM_CONTAINER_CACHE')) define('CIVICRM_CONTAINER_CACHE', 'auto'); if (!defined('CIVICRM_MYSQL_STRICT')) define('CIVICRM_MYSQL_STRICT', true); -- 2.25.1