int $seconds). */ protected $maxTimeouts; /** * @var array * List of cache instances, with fastest/closest first. * Array(int $tierNum => CRM_Utils_Cache_Interface). */ protected $tiers; /** * CRM_Utils_Cache_Tiered constructor. * @param array $tiers * List of cache instances, with fastest/closest first. * Must be indexed numerically (0, 1, 2...). * @param array $maxTimeouts * A list of maximum timeouts for each cache-tier. * There must be at least one value in this array. * If timeouts are omitted for slower tiers, they are filled in with the last value. * @throws CRM_Core_Exception */ public function __construct($tiers, $maxTimeouts = [86400]) { $this->tiers = $tiers; $this->maxTimeouts = []; foreach ($tiers as $k => $tier) { $this->maxTimeouts[$k] = isset($maxTimeouts[$k]) ? $maxTimeouts[$k] : $this->maxTimeouts[$k - 1]; } for ($far = 1; $far < count($tiers); $far++) { $near = $far - 1; if ($this->maxTimeouts[$near] > $this->maxTimeouts[$far]) { throw new \CRM_Core_Exception("Invalid configuration: Near cache #{$near} has longer timeout than far cache #{$far}"); } } } public function set($key, $value, $ttl = NULL) { if ($ttl !== NULL & !is_int($ttl) && !($ttl instanceof DateInterval)) { throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache TTL"); } foreach ($this->tiers as $tierNum => $tier) { /** @var CRM_Utils_Cache_Interface $tier */ $effTtl = $this->getEffectiveTtl($tierNum, $ttl); $expiresAt = CRM_Utils_Date::convertCacheTtlToExpires($effTtl, $this->maxTimeouts[$tierNum]); if (!$tier->set($key, [0 => $expiresAt, 1 => $value], $effTtl)) { return FALSE; } } return TRUE; } public function get($key, $default = NULL) { $nack = CRM_Utils_Cache::nack(); foreach ($this->tiers as $readTierNum => $tier) { /** @var CRM_Utils_Cache_Interface $tier */ $wrapped = $tier->get($key, $nack); if ($wrapped !== $nack && $wrapped[0] >= CRM_Utils_Time::getTimeRaw()) { list ($parentExpires, $value) = $wrapped; // (Re)populate the faster caches; and then return the value we found. for ($i = 0; $i < $readTierNum; $i++) { $now = CRM_Utils_Time::getTimeRaw(); $effExpires = min($parentExpires, $now + $this->maxTimeouts[$i]); $this->tiers[$i]->set($key, [0 => $effExpires, 1 => $value], $effExpires - $now); } return $value; } } return $default; } public function delete($key) { foreach ($this->tiers as $tier) { /** @var CRM_Utils_Cache_Interface $tier */ $tier->delete($key); } return TRUE; } public function flush() { return $this->clear(); } public function clear() { foreach ($this->tiers as $tier) { /** @var CRM_Utils_Cache_Interface $tier */ if (!$tier->clear()) { return FALSE; } } return TRUE; } public function has($key) { $nack = CRM_Utils_Cache::nack(); foreach ($this->tiers as $tier) { /** @var CRM_Utils_Cache_Interface $tier */ $wrapped = $tier->get($key, $nack); if ($wrapped !== $nack && $wrapped[0] > CRM_Utils_Time::getTimeRaw()) { return TRUE; } } return FALSE; } protected function getEffectiveTtl($tierNum, $ttl) { if ($ttl === NULL) { return $this->maxTimeouts[$tierNum]; } else { return min($this->maxTimeouts[$tierNum], $ttl); } } }