Elgg  Version master
CompositeCache.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Cache;
4 
5 use Elgg\Config;
8 use Elgg\Values;
13 
19 class CompositeCache extends BaseCache {
20 
24  protected $ttl = 86400;
25 
29  protected $config;
30 
34  protected $flags;
35 
39  protected $pool;
40 
44  protected $namespace;
45 
50 
59  public function __construct($namespace, Config $config, $flags, bool $validate_lastcache = true) {
60  parent::__construct();
61 
62  $this->namespace = $namespace;
63  $this->config = $config;
64  $this->flags = $flags;
65  $this->pool = $this->createPool();
66  $this->validate_lastcache = $validate_lastcache;
67  }
68 
78  public function save($key, $data, $expire_after = null) {
79  if ($this->disabled) {
80  return false;
81  }
82 
83  $expire_after = $expire_after ?: $this->ttl;
84 
85  $item = $this->pool->getItem($this->sanitizeItemKey($key));
86  $item->set($data);
87 
88  if (is_int($expire_after)) {
89  $item->expiresAfter($expire_after);
90  } elseif ($expire_after instanceof \DateTime) {
91  $item->expiresAt($expire_after);
92  }
93 
94  return $this->pool->save($item);
95  }
96 
104  public function load($key) {
105  if ($this->disabled) {
106  return null;
107  }
108 
109  $item = $this->pool->getItem($this->sanitizeItemKey($key));
110  if (!$item->isHit()) {
111  return null;
112  }
113 
114  if ($this->validate_lastcache && $this->config->lastcache) {
115  $expiration_date = Values::normalizeTime($this->config->lastcache);
116 
117  if ($item->getCreationDate()->getTimestamp() < $expiration_date->getTimestamp()) {
118  $this->delete($key);
119  return null;
120  }
121  }
122 
123  return $item->get();
124  }
125 
129  public function delete($key) {
130  if ($this->disabled) {
131  return false;
132  }
133 
134  return $this->pool->deleteItem($this->sanitizeItemKey($key));
135  }
136 
140  public function clear() {
141  return $this->pool->clear();
142  }
143 
147  public function invalidate() {
148  // Phpfastcache doesn't have invalidation as an action.
149  // This is handled during load
150  return true;
151  }
152 
156  public function purge() {
157  // Phpfastcache doesn't have purge as an action
158  return true;
159  }
160 
170  public function setNamespace($namespace = 'default') {
171  $this->namespace = $namespace;
172  }
173 
179  public function getNamespace() {
180  return $this->namespace;
181  }
182 
192  protected function prefixInstanceId(string $id): string {
193  return "{$this->getNamespace()}_{$id}";
194  }
195 
207  protected function sanitizeItemKey($key): string {
208  if (!is_string($key) && !is_int($key)) {
209  throw new InvalidArgumentException('key must be string or integer');
210  }
211 
212  return str_replace(['{', '}', '(', ')', '/', '\\', '@', ':'], '_', "{$key}");
213  }
214 
221  protected function createPool() {
222 
223  $drivers = [];
224  $drivers[] = $this->buildRedisDriver();
225  $drivers[] = $this->buildMemcachedDriver();
226  $drivers[] = $this->buildFileSystemDriver();
227  $drivers[] = $this->buildLocalFileSystemDriver();
228  $drivers[] = $this->buildBlackHoleDriver();
229  $drivers = array_filter($drivers);
230 
231  if (empty($drivers)) {
232  // the memory driver can only be used as a stand-alone driver (not combined in a cluster)
233  // other drivers already have a built in memory storage (default allowed in config)
234  $ephemeral = $this->buildEphemeralDriver();
235  if (!empty($ephemeral)) {
236  $drivers[] = $ephemeral;
237  }
238  }
239 
240  if (empty($drivers)) {
241  throw new ConfigurationException('Unable to initialize composite cache without drivers');
242  }
243 
244  if (count($drivers) === 1) {
245  return array_shift($drivers);
246  }
247 
248  $cluster = new ClusterAggregator($this->getNamespace());
249  foreach ($drivers as $driver) {
250  $cluster->aggregateDriver($driver);
251  }
252 
253  $cluster_driver = $cluster->getCluster();
254  $cluster_driver->setConfig(new ConfigurationOption([
255  'preventCacheSlams' => true,
256  'useStaticItemCaching' => true,
257  'itemDetailedDate' => true,
258  ]));
259 
260  return $cluster_driver;
261  }
262 
267  protected function buildRedisDriver() {
268  if (!($this->flags & ELGG_CACHE_PERSISTENT)) {
269  return null;
270  }
271 
272  if (!self::isRedisAvailable()) {
273  return null;
274  }
275 
276  $config = \Elgg\Cache\Config\Redis::fromElggConfig($this->namespace, $this->config);
277  if (empty($config)) {
278  return null;
279  }
280 
281  return CacheManager::getInstance('Redis', $config, $this->prefixInstanceId('redis'));
282  }
283 
288  protected function buildMemcachedDriver() {
289  if (!($this->flags & ELGG_CACHE_PERSISTENT)) {
290  return null;
291  }
292 
293  if (!self::isMemcacheAvailable()) {
294  return null;
295  }
296 
297  $config = \Elgg\Cache\Config\Memcached::fromElggConfig($this->namespace, $this->config);
298  if (empty($config)) {
299  return null;
300  }
301 
302  return CacheManager::getInstance('Memcached', $config, $this->prefixInstanceId('memcache'));
303  }
304 
309  protected function buildFileSystemDriver() {
310  if (!($this->flags & ELGG_CACHE_FILESYSTEM)) {
311  return null;
312  }
313 
314  $config = \Elgg\Cache\Config\Files::fromElggConfig($this->namespace, $this->config);
315  if (empty($config)) {
316  return null;
317  }
318 
319  try {
320  return CacheManager::getInstance('Files', $config, $this->prefixInstanceId('files'));
321  } catch (\Phpfastcache\Exceptions\PhpfastcacheIOException $e) {
322  if (!$this->config->installer_running) {
323  elgg_log($e, 'ERROR');
324  }
325  }
326 
327  return null;
328  }
329 
334  protected function buildLocalFileSystemDriver() {
335  if (!($this->flags & ELGG_CACHE_LOCALFILESYSTEM)) {
336  return null;
337  }
338 
339  $config = \Elgg\Cache\Config\LocalFiles::fromElggConfig($this->namespace, $this->config);
340  if (empty($config)) {
341  return null;
342  }
343 
344  try {
345  return CacheManager::getInstance('Files', $config, $this->prefixInstanceId('local_files'));
346  } catch (\Phpfastcache\Exceptions\PhpfastcacheIOException $e) {
347  if (!$this->config->installer_running) {
348  elgg_log($e, 'ERROR');
349  }
350  }
351 
352  return null;
353  }
354 
359  protected function buildEphemeralDriver() {
360  if (!($this->flags & ELGG_CACHE_RUNTIME)) {
361  return null;
362  }
363 
364  $config = new \Phpfastcache\Drivers\Memstatic\Config();
365 
366  $config->setUseStaticItemCaching(true);
367  $config->setItemDetailedDate(true);
368 
369  return CacheManager::getInstance('Memstatic', $config, $this->prefixInstanceId('memstatic'));
370  }
371 
376  protected function buildBlackHoleDriver() {
377  if (!($this->flags & ELGG_CACHE_BLACK_HOLE)) {
378  return null;
379  }
380 
381  $config = new \Phpfastcache\Drivers\Devnull\Config();
382 
383  return CacheManager::getInstance('Devnull', $config, $this->prefixInstanceId('devnull'));
384  }
385 
393  public static function isMemcacheAvailable(): bool {
394  return class_exists('Memcached');
395  }
396 
404  public static function isRedisAvailable(): bool {
405  return extension_loaded('Redis');
406  }
407 }
if(!$items) $item
Definition: delete.php:13
static isMemcacheAvailable()
Helper function to check if memcache is available.
$ttl
TTL of saved items (default timeout after a day to prevent anything getting too stale) ...
A generic parent class for Configuration exceptions.
Exception thrown if an argument is not of the expected type.
static fromElggConfig(string $namespace,\Elgg\Config $config)
Factory to return a config object to be used when starting a driver.
Definition: Memcached.php:23
Saves user notification settings.
getNamespace()
Get the namespace currently defined.
load($key)
Load data from the cache using a given key.
const ELGG_CACHE_BLACK_HOLE
Cache init values.
Definition: constants.php:121
prefixInstanceId(string $id)
Prefixes instance ids with namespace.
buildEphemeralDriver()
Builds in-memory driver.
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof\ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
setNamespace($namespace= 'default')
Set the namespace of this cache.
Composite cache pool.
sanitizeItemKey($key)
Sanitizes item key for cache.
if(!$entity instanceof\ElggUser) $data
Definition: attributes.php:13
buildMemcachedDriver()
Builds Memcached driver.
const ELGG_CACHE_RUNTIME
Definition: constants.php:122
save($key, $data, $expire_after=null)
Save data in a cache.
static fromElggConfig(string $namespace,\Elgg\Config $config)
Factory to return a config object to be used when starting a driver.
Definition: LocalFiles.php:23
elgg_log($message, $level=\Psr\Log\LogLevel::NOTICE)
Log a message.
Definition: elgglib.php:86
static fromElggConfig(string $namespace,\Elgg\Config $config)
Factory to return a config object to be used when starting a driver.
Definition: Files.php:23
The Elgg cache base class.
Definition: BaseCache.php:9
if($container instanceof ElggGroup &&$container->guid!=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
const ELGG_CACHE_LOCALFILESYSTEM
Definition: constants.php:125
createPool()
Create a new cluster/pool of drivers.
const ELGG_CACHE_PERSISTENT
Definition: constants.php:124
const ELGG_CACHE_FILESYSTEM
Definition: constants.php:123
static isRedisAvailable()
Helper function to check if Redis is available.
buildRedisDriver()
Builds Redis driver.
static normalizeTime($time)
Returns DateTime object based on time representation.
Definition: Values.php:75
buildLocalFileSystemDriver()
Builds local file system driver.
buildBlackHoleDriver()
Builds null cache driver.
$id
Generic annotation delete action.
Definition: delete.php:6
buildFileSystemDriver()
Builds file system driver.
static fromElggConfig(string $namespace,\Elgg\Config $config)
Factory to return a config object to be used when starting a driver.
Definition: Redis.php:23
__construct($namespace, Config $config, $flags, bool $validate_lastcache=true)
Constructor.