* @copyright (c) 2010-16 by Ralf Becker * @version $Id$ */ namespace EGroupware\Api\Cache; /** * Caching provider storing data in PHP's APCu extension / shared memory. * * The provider concats all $keys with '::' to get a single string. * * This provider is used by default, if it is available or explicit enabled in your header.inc.php: * $GLOBALS['egw_info']['server']['cache_provider_instance'] = array('EGroupware\Api\Cache\Apc'); * and optional also $GLOBALS['egw_info']['server']['cache_provider_tree'] (defaults to instance) * * APC(u) and CLI: * -------------- * APC(u) is not enabled by default for CLI (apc.enable_cli), nor would it access same shared memory! * It makes no sense to fall back to files cache, as this is probably quite outdated, * if PHP via Webserver uses APC. Better to use no cache at all. * Api\Cache::get*() will return NULL for not found and Api\Cache::[un]set*() * false for not being able to (un)set anything. * It also does not make sense to report failure by throwing an Exception and filling * up cron logs. * --> if APC(u) is available for Webserver, we report availability for CLI too, * but use no cache at all! */ class Apcu extends Base implements Provider { /** * Constructor, eg. opens the connection to the backend * * @throws Exception if connection to backend could not be established * @param array $params eg. array('localhost'[,'localhost:11211',...]) */ function __construct(array $params) { if (!function_exists('apcu_fetch')) // apc >= 3.0 { throw new Exception (__METHOD__.'('.array2string($params).") No function apcu_fetch()!"); } } /** * Check if APC is available for caching user data * * Default shared memory size of 32M, which is used only for user data in APCu. * Unlike APC which shares the total memory with it's opcode cache 32M is ok * for a small install. * * @return boolean true: apc available, false: not */ public static function available() { if (($available = (bool)ini_get('apc.enabled') && function_exists('apcu_fetch'))) { $size = ini_get('apc.shm_size'); switch(strtoupper(substr($size, -1))) { case 'G': $size *= 1024; case 'M': $size *= 1024; case 'K': $size *= 1024; } $size *= ini_get('apc.shm_segments'); // only cache in APCu, if we have at least 32M available (default is 32M) $available = $size >= 33554432; } //error_log(__METHOD__."() size=$size returning ".array2string($available)); return $available; } /** * Stores some data in the cache, if it does NOT already exists there * * @param array $keys eg. array($level,$app,$location) * @param mixed $data * @param int $expiration =0 * @return boolean true on success, false on error, incl. key already exists in cache */ function add(array $keys,$data,$expiration=0) { return apcu_add(self::key($keys),$data,$expiration); } /** * Stores some data in the cache * * @param array $keys eg. array($level,$app,$location) * @param mixed $data * @param int $expiration =0 * @return boolean true on success, false on error */ function set(array $keys,$data,$expiration=0) { return apcu_store(self::key($keys),$data,$expiration); } /** * Get some data from the cache * * @param array $keys eg. array($level,$app,$location) * @return mixed data stored or NULL if not found in cache */ function get(array $keys) { $success = null; $data = apcu_fetch($key=self::key($keys),$success); if (!$success) { //error_log(__METHOD__."(".array2string($keys).") key='$key' NOT found!"); return null; } //error_log(__METHOD__."(".array2string($keys).") key='$key' found ".bytes(serialize($data))." bytes)."); return $data; } /** * Delete some data from the cache * * @param array $keys eg. array($level,$app,$location) * @return boolean true on success, false on error (eg. $key not set) */ function delete(array $keys) { return apcu_delete(self::key($keys)); } /** * Delete all data under given keys * * If no keys are given whole APCu cache is cleared, which should allways * work and can not run out of memory as the iterator sometimes does. * * @param array $keys eg. array($level,$app,$location) or array() to clear whole cache * @return boolean true on success, false on error (eg. on iterator available) */ function flush(array $keys) { // do NOT try instanciating APCuIterator, if APCu is not enabled, as it gives a PHP Fatal Error if (!ini_get('apc.enabled') || php_sapi_name() === 'cli' && !ini_get('apc.cli_enabled')) { return false; } if (!$keys && function_exists('apcu_clear_cache')) { apcu_clear_cache(); return true; } // APCu > 5 has APCUIterator if (class_exists('APCUIterator')) { $iterator = new \APCUIterator($preg='/^'.preg_quote(self::key($keys).'/')); } // APC >= 3.1.1, but also seems to be missing if apc is disabled eg. for cli elseif(class_exists('APCIterator')) { $iterator = new \APCIterator('user', $preg='/^'.preg_quote(self::key($keys).'/')); } else { if (function_exists('apcu_clear_cache')) apcu_clear_cache(); return false; } foreach($iterator as $item) { //error_log(__METHOD__."(".array2string($keys).") preg='$preg': calling apcu_delete('$item[key]')"); apcu_delete($item['key']); } return true; } /** * Create a single key from $keys * * @param array $keys * @return string */ private static function key(array $keys) { return implode('::',$keys); } }