diff --git a/CRM/Utils/Cache/APCcache.php b/CRM/Utils/Cache/APCcache.php index e696aa19c3d2..a8b4e5cd6949 100644 --- a/CRM/Utils/Cache/APCcache.php +++ b/CRM/Utils/Cache/APCcache.php @@ -31,6 +31,10 @@ * @copyright CiviCRM LLC (c) 2004-2018 */ class CRM_Utils_Cache_APCcache implements CRM_Utils_Cache_Interface { + + use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation. + use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation + const DEFAULT_TIMEOUT = 3600; const DEFAULT_PREFIX = ''; @@ -72,10 +76,14 @@ public function __construct(&$config) { /** * @param $key * @param $value + * @param null|int|\DateInterval $ttl * * @return bool */ - public function set($key, &$value) { + public function set($key, $value, $ttl = NULL) { + if ($ttl !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL"); + } if (!apc_store($this->_prefix . $key, $value, $this->_timeout)) { return FALSE; } @@ -84,10 +92,14 @@ public function set($key, &$value) { /** * @param $key + * @param mixed $default * * @return mixed */ - public function get($key) { + public function get($key, $default = NULL) { + if ($default !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default"); + } return apc_fetch($this->_prefix . $key); } @@ -113,6 +125,11 @@ public function flush() { apc_delete($this->_prefix . $name); } } + return TRUE; + } + + public function clear() { + return $this->flush(); } } diff --git a/CRM/Utils/Cache/ArrayCache.php b/CRM/Utils/Cache/ArrayCache.php index b2272fa1eb6c..c74e1900f1bc 100644 --- a/CRM/Utils/Cache/ArrayCache.php +++ b/CRM/Utils/Cache/ArrayCache.php @@ -36,6 +36,9 @@ */ class CRM_Utils_Cache_Arraycache implements CRM_Utils_Cache_Interface { + use CRM_Utils_Cache_NaiveMultipleTrait; + use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation + /** * The cache storage container, an in memory array by default */ @@ -56,30 +59,44 @@ public function __construct($config) { /** * @param string $key * @param mixed $value + * @param null|int|\DateInterval $ttl + * @return bool */ - public function set($key, &$value) { + public function set($key, $value, $ttl = NULL) { + if ($ttl !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL"); + } $this->_cache[$key] = $value; + return TRUE; } /** * @param string $key + * @param mixed $default * * @return mixed */ - public function get($key) { - return CRM_Utils_Array::value($key, $this->_cache); + public function get($key, $default = NULL) { + return CRM_Utils_Array::value($key, $this->_cache, $default); } /** * @param string $key + * @return bool */ public function delete($key) { unset($this->_cache[$key]); + return TRUE; } public function flush() { unset($this->_cache); $this->_cache = array(); + return TRUE; + } + + public function clear() { + return $this->flush(); } } diff --git a/CRM/Utils/Cache/Interface.php b/CRM/Utils/Cache/Interface.php index f11327cb59c7..9effdd9d3aa6 100644 --- a/CRM/Utils/Cache/Interface.php +++ b/CRM/Utils/Cache/Interface.php @@ -30,60 +30,78 @@ * @package CRM * @copyright CiviCRM LLC (c) 2004-2018 * - * CRM_Utils_Cache_Interface + * CRM_Utils_Cache_Interface is a long-standing interface used within CiviCRM + * for interacting with a cache service. In style and substance, it is extremely + * similar to PHP-FIG's SimpleCache interface (PSR-16). Consequently, beginning + * with CiviCRM v5.4, this extends \Psr\SimpleCache\CacheInterface. * - * PHP-FIG has been developing a draft standard for caching, - * PSR-6. The standard has not been ratified yet. When - * making changes to this interface, please take care to - * avoid *conflicst* with PSR-6's CacheItemPoolInterface. At - * time of writing, they do not conflict. Avoiding conflicts - * will enable more transition paths where Civi - * simultaneously supports both interfaces in the same - * implementation. - * - * For example, the current interface defines: - * - * function get($key) => mixed $value - * - * and PSR-6 defines: - * - * function getItem($key) => ItemInterface $item - * - * These are different styles (e.g. "weak item" vs "strong item"), - * but the two methods do not *conflict*. They can coexist, - * and you can trivially write adapters between the two. - * - * @see https://github.com/php-fig/fig-standards/blob/master/proposed/cache.md + * @see https://www.php-fig.org/psr/psr-16/ */ -interface CRM_Utils_Cache_Interface { +interface CRM_Utils_Cache_Interface extends \Psr\SimpleCache\CacheInterface { /** * Set the value in the cache. * * @param string $key * @param mixed $value + * @param null|int|\DateInterval $ttl + * @return bool */ - public function set($key, &$value); + public function set($key, $value, $ttl = NULL); /** * Get a value from the cache. * * @param string $key + * @param mixed $default * @return mixed - * NULL if $key has not been previously set + * The previously set value value, or $default (NULL). */ - public function get($key); + public function get($key, $default = NULL); /** * Delete a value from the cache. * * @param string $key + * @return bool */ public function delete($key); /** * Delete all values from the cache. + * + * NOTE: flush() and clear() should be aliases. flush() is specified by + * Civi's traditional interface, and clear() is specified by PSR-16. + * + * @return bool + * @see clear + * @deprecated */ public function flush(); + /** + * Delete all values from the cache. + * + * NOTE: flush() and clear() should be aliases. flush() is specified by + * Civi's traditional interface, and clear() is specified by PSR-16. + * + * @return bool + * @see flush + */ + public function clear(); + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + */ + public function has($key); + } diff --git a/CRM/Utils/Cache/Memcache.php b/CRM/Utils/Cache/Memcache.php index 630589e3d861..8e6e01dc51bb 100644 --- a/CRM/Utils/Cache/Memcache.php +++ b/CRM/Utils/Cache/Memcache.php @@ -31,6 +31,10 @@ * @copyright CiviCRM LLC (c) 2004-2018 */ class CRM_Utils_Cache_Memcache implements CRM_Utils_Cache_Interface { + + use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation. + use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation + const DEFAULT_HOST = 'localhost'; const DEFAULT_PORT = 11211; const DEFAULT_TIMEOUT = 3600; @@ -109,10 +113,14 @@ public function __construct($config) { /** * @param $key * @param $value + * @param null|int|\DateInterval $ttl * * @return bool */ - public function set($key, &$value) { + public function set($key, $value, $ttl = NULL) { + if ($ttl !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL"); + } if (!$this->_cache->set($this->_prefix . $key, $value, FALSE, $this->_timeout)) { return FALSE; } @@ -121,10 +129,14 @@ public function set($key, &$value) { /** * @param $key + * @param mixed $default * * @return mixed */ - public function &get($key) { + public function get($key, $default = NULL) { + if ($default !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default"); + } $result = $this->_cache->get($this->_prefix . $key); return $result; } @@ -132,18 +144,22 @@ public function &get($key) { /** * @param $key * - * @return mixed + * @return bool */ public function delete($key) { return $this->_cache->delete($this->_prefix . $key); } /** - * @return mixed + * @return bool */ public function flush() { // FIXME: Only delete items matching `$this->_prefix`. return $this->_cache->flush(); } + public function clear() { + return $this->flush(); + } + } diff --git a/CRM/Utils/Cache/Memcached.php b/CRM/Utils/Cache/Memcached.php index 07315e53cebe..c1ad9cb75aaf 100644 --- a/CRM/Utils/Cache/Memcached.php +++ b/CRM/Utils/Cache/Memcached.php @@ -31,6 +31,10 @@ * @copyright CiviCRM LLC (c) 2004-2018 */ class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface { + + use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation. + use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation + const DEFAULT_HOST = 'localhost'; const DEFAULT_PORT = 11211; const DEFAULT_TIMEOUT = 3600; @@ -110,11 +114,15 @@ public function __construct($config) { /** * @param $key * @param $value + * @param null|int|\DateInterval $ttl * * @return bool * @throws Exception */ - public function set($key, &$value) { + public function set($key, $value, $ttl = NULL) { + if ($ttl !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL"); + } $key = $this->cleanKey($key); if (!$this->_cache->set($key, $value, $this->_timeout)) { CRM_Core_Error::debug('Result Code: ', $this->_cache->getResultMessage()); @@ -126,10 +134,14 @@ public function set($key, &$value) { /** * @param $key + * @param mixed $default * * @return mixed */ - public function &get($key) { + public function get($key, $default = NULL) { + if ($default !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default"); + } $key = $this->cleanKey($key); $result = $this->_cache->get($key); return $result; @@ -161,11 +173,15 @@ public function cleanKey($key) { } /** - * @return mixed + * @return bool */ public function flush() { // FIXME: Only delete items matching `$this->_prefix`. return $this->_cache->flush(); } + public function clear() { + return $this->flush(); + } + } diff --git a/CRM/Utils/Cache/NaiveHasTrait.php b/CRM/Utils/Cache/NaiveHasTrait.php new file mode 100644 index 000000000000..78ecc6788763 --- /dev/null +++ b/CRM/Utils/Cache/NaiveHasTrait.php @@ -0,0 +1,49 @@ +get($key, NULL) === NULL); + $hasDefaultB = ($this->get($key, 123) === 123); + return !($hasDefaultA && $hasDefaultB); + } + +} diff --git a/CRM/Utils/Cache/NaiveMultipleTrait.php b/CRM/Utils/Cache/NaiveMultipleTrait.php new file mode 100644 index 000000000000..b1f37a44908c --- /dev/null +++ b/CRM/Utils/Cache/NaiveMultipleTrait.php @@ -0,0 +1,102 @@ + value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = NULL) { + $result = []; + foreach ($keys as $key) { + $result[$key] = $this->get($key, $default); + } + return $result; + } + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = NULL) { + $result = TRUE; + foreach ($values as $key => $value) { + $result = $this->set($key, $value, $ttl) || $result; + } + return $result; + } + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys) { + $result = TRUE; + foreach ($keys as $key) { + $result = $this->delete($key) || $result; + } + return $result; + } + +} diff --git a/CRM/Utils/Cache/NoCache.php b/CRM/Utils/Cache/NoCache.php index 66c6b74cff41..3c1d9b944527 100644 --- a/CRM/Utils/Cache/NoCache.php +++ b/CRM/Utils/Cache/NoCache.php @@ -32,6 +32,9 @@ */ class CRM_Utils_Cache_NoCache implements CRM_Utils_Cache_Interface { + use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation. + use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation + /** * We only need one instance of this object. So we use the singleton * pattern and cache the instance in this variable @@ -54,20 +57,22 @@ public function __construct($config) { /** * @param string $key * @param mixed $value + * @param null|int|\DateInterval $ttl * * @return bool */ - public function set($key, &$value) { + public function set($key, $value, $ttl = NULL) { return FALSE; } /** * @param string $key + * @param mixed $default * * @return null */ - public function get($key) { - return NULL; + public function get($key, $default = NULL) { + return $default; } /** @@ -86,4 +91,8 @@ public function flush() { return FALSE; } + public function clear() { + return $this->flush(); + } + } diff --git a/CRM/Utils/Cache/Redis.php b/CRM/Utils/Cache/Redis.php index 4988b8beef8d..01fc538c9355 100644 --- a/CRM/Utils/Cache/Redis.php +++ b/CRM/Utils/Cache/Redis.php @@ -33,6 +33,10 @@ * */ class CRM_Utils_Cache_Redis implements CRM_Utils_Cache_Interface { + + use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation. + use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation + const DEFAULT_HOST = 'localhost'; const DEFAULT_PORT = 6379; const DEFAULT_TIMEOUT = 3600; @@ -113,11 +117,15 @@ public function __construct($config) { /** * @param $key * @param $value + * @param null|int|\DateInterval $ttl * * @return bool * @throws Exception */ - public function set($key, &$value) { + public function set($key, $value, $ttl = NULL) { + if ($ttl !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL"); + } if (!$this->_cache->set($this->_prefix . $key, serialize($value), $this->_timeout)) { if (PHP_SAPI === 'cli' || (Civi\Core\Container::isContainerBooted() && CRM_Core_Permission::check('view debug output'))) { CRM_Core_Error::fatal("Redis set ($key) failed: " . $this->_cache->getLastError()); @@ -133,25 +141,27 @@ public function set($key, &$value) { /** * @param $key + * @param mixed $default * * @return mixed */ - public function get($key) { + public function get($key, $default = NULL) { $result = $this->_cache->get($this->_prefix . $key); - return ($result === FALSE) ? NULL : unserialize($result); + return ($result === FALSE) ? $default : unserialize($result); } /** * @param $key * - * @return mixed + * @return bool */ public function delete($key) { - return $this->_cache->delete($this->_prefix . $key); + $this->_cache->delete($this->_prefix . $key); + return TRUE; } /** - * @return mixed + * @return bool */ public function flush() { // FIXME: Ideally, we'd map each prefix to a different 'hash' object in Redis, @@ -159,7 +169,12 @@ public function flush() { // more general rethink of cache expiration/TTL. $keys = $this->_cache->keys($this->_prefix . '*'); - return $this->_cache->del($keys); + $this->_cache->del($keys); + return TRUE; + } + + public function clear() { + return $this->flush(); } } diff --git a/CRM/Utils/Cache/SerializeCache.php b/CRM/Utils/Cache/SerializeCache.php index 6164694c3e5c..fdf0eb6d47b5 100644 --- a/CRM/Utils/Cache/SerializeCache.php +++ b/CRM/Utils/Cache/SerializeCache.php @@ -36,6 +36,9 @@ */ class CRM_Utils_Cache_SerializeCache implements CRM_Utils_Cache_Interface { + use CRM_Utils_Cache_NaiveMultipleTrait; + use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation + /** * The cache storage container, an array by default, stored in a file under templates */ @@ -67,10 +70,15 @@ public function fileName($key) { /** * @param string $key + * @param mixed $default * * @return mixed */ - public function get($key) { + public function get($key, $default = NULL) { + if ($default !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default"); + } + if (array_key_exists($key, $this->_cache)) { return $this->_cache[$key]; } @@ -85,32 +93,41 @@ public function get($key) { /** * @param string $key * @param mixed $value + * @param null|int|\DateInterval $ttl + * @return bool */ - public function set($key, &$value) { + public function set($key, $value, $ttl = NULL) { + if ($ttl !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL"); + } if (file_exists($this->fileName($key))) { - return; + return FALSE; // WTF, write-once cache?! } $this->_cache[$key] = $value; - file_put_contents($this->fileName($key), "fileName($key), "fileName($key))) { unlink($this->fileName($key)); } unset($this->_cache[$key]); + return TRUE; } /** * @param null $key + * @return bool */ public function flush($key = NULL) { $prefix = "CRM_"; if (!$handle = opendir(CIVICRM_TEMPLATE_COMPILEDIR)) { - return; // die? Error? + return FALSE; // die? Error? } while (FALSE !== ($entry = readdir($handle))) { if (substr($entry, 0, 4) == $prefix) { @@ -120,6 +137,11 @@ public function flush($key = NULL) { closedir($handle); unset($this->_cache); $this->_cache = array(); + return TRUE; + } + + public function clear() { + return $this->flush(); } } diff --git a/CRM/Utils/Cache/SqlGroup.php b/CRM/Utils/Cache/SqlGroup.php index 0bb8e98e277a..3b983ae10632 100644 --- a/CRM/Utils/Cache/SqlGroup.php +++ b/CRM/Utils/Cache/SqlGroup.php @@ -38,6 +38,9 @@ */ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface { + use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation. + use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation + /** * The host name of the memcached server. * @@ -89,18 +92,28 @@ public function __construct($config) { /** * @param string $key * @param mixed $value + * @param null|int|\DateInterval $ttl + * @return bool */ - public function set($key, &$value) { + public function set($key, $value, $ttl = NULL) { + if ($ttl !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL"); + } CRM_Core_BAO_Cache::setItem($value, $this->group, $key, $this->componentID); $this->frontCache[$key] = $value; + return TRUE; } /** * @param string $key + * @param mixed $default * * @return mixed */ - public function get($key) { + public function get($key, $default = NULL) { + if ($default !== NULL) { + throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default"); + } if (!array_key_exists($key, $this->frontCache)) { $this->frontCache[$key] = CRM_Core_BAO_Cache::getItem($this->group, $key, $this->componentID); } @@ -119,12 +132,14 @@ public function getFromFrontCache($key, $default = NULL) { /** * @param string $key + * @return bool */ public function delete($key) { CRM_Core_BAO_Cache::deleteGroup($this->group, $key, FALSE); CRM_Core_BAO_Cache::$_cache = NULL; // FIXME: remove multitier cache CRM_Utils_Cache::singleton()->flush(); // FIXME: remove multitier cache unset($this->frontCache[$key]); + return TRUE; } public function flush() { @@ -132,6 +147,11 @@ public function flush() { CRM_Core_BAO_Cache::$_cache = NULL; // FIXME: remove multitier cache CRM_Utils_Cache::singleton()->flush(); // FIXME: remove multitier cache $this->frontCache = array(); + return TRUE; + } + + public function clear() { + return $this->flush(); } public function prefetch() { diff --git a/Civi.php b/Civi.php index ae5dc6f4b1dd..8acfdba76862 100644 --- a/Civi.php +++ b/Civi.php @@ -26,20 +26,15 @@ class Civi { public static $statics = array(); /** - * EXPERIMENTAL. Retrieve a named cache instance. - * - * This interface is flagged as experimental due to political - * ambiguity in PHP community -- PHP-FIG has an open but - * somewhat controversial draft standard for caching. Based on - * the current draft, it's expected that this function could - * simultaneously support both CRM_Utils_Cache_Interface and - * PSR-6, but that depends on whether PSR-6 changes any more. + * Retrieve a named cache instance. * * @param string $name * The name of the cache. The 'default' cache is biased toward * high-performance caches (eg memcache/redis/apc) when * available and falls back to single-request (static) caching. * @return CRM_Utils_Cache_Interface + * NOTE: Beginning in CiviCRM v5.4, the cache instance complies with + * PSR-16 (\Psr\SimpleCache\CacheInterface). */ public static function cache($name = 'default') { return \Civi\Core\Container::singleton()->get('cache.' . $name); diff --git a/composer.json b/composer.json index 9d81135dbbec..1078ccf3d303 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,8 @@ "pear/Net_SMTP": "1.6.*", "pear/Net_socket": "1.0.*", "civicrm/civicrm-setup": "~0.2.0", - "guzzlehttp/guzzle": "^6.3" + "guzzlehttp/guzzle": "^6.3", + "psr/simple-cache": "~1.0.1" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index 0cce99a2a89b..8f3e4d2659e9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "9c5441f5ce4c51ed3a8cc326693cd904", + "content-hash": "233f9c457d9e7d49a6d96c356e1035f1", "packages": [ { "name": "civicrm/civicrm-cxn-rpc", @@ -1131,6 +1131,54 @@ ], "time": "2012-12-21T11:40:51+00:00" }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + }, { "name": "sabberworm/php-css-parser", "version": "6.0.1",