Elgg  Version 6.0
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;
14 
20 class CompositeCache extends BaseCache {
21 
25  protected $ttl = 86400;
26 
30  protected $config;
31 
35  protected $flags;
36 
40  protected $pool;
41 
45  protected $namespace;
46 
51 
60  public function __construct($namespace, Config $config, $flags, bool $validate_lastcache = true) {
61  parent::__construct();
62 
63  $this->namespace = $namespace;
64  $this->config = $config;
65  $this->flags = $flags;
66  $this->pool = $this->createPool();
67  $this->validate_lastcache = $validate_lastcache;
68  }
69 
79  public function save($key, $data, $expire_after = null) {
80  if ($this->disabled) {
81  return false;
82  }
83 
84  $expire_after = $expire_after ?: $this->ttl;
85 
86  $item = $this->pool->getItem($this->sanitizeItemKey($key));
87  $item->set($data);
88 
89  if (is_int($expire_after)) {
90  $item->expiresAfter($expire_after);
91  } elseif ($expire_after instanceof \DateTime) {
92  $item->expiresAt($expire_after);
93  }
94 
95  return $this->pool->save($item);
96  }
97 
105  public function load($key) {
106  if ($this->disabled) {
107  return null;
108  }
109 
110  try {
111  $item = $this->pool->getItem($this->sanitizeItemKey($key));
112  if (!$item->isHit()) {
113  return null;
114  }
115 
116  if ($this->validate_lastcache && $this->config->lastcache) {
117  $expiration_date = Values::normalizeTime($this->config->lastcache);
118 
119  if ($item->getCreationDate()->getTimestamp() < $expiration_date->getTimestamp()) {
120  $this->delete($key);
121  return null;
122  }
123  }
124 
125  return $item->get();
126  } catch (PhpfastcacheRootException $e) {
127  // something wrong with the cache
128  elgg_log($e->getMessage(), 'ERROR');
129  return null;
130  }
131  }
132 
136  public function delete($key) {
137  if ($this->disabled) {
138  return false;
139  }
140 
141  return $this->pool->deleteItem($this->sanitizeItemKey($key));
142  }
143 
147  public function clear() {
148  return $this->pool->clear();
149  }
150 
154  public function invalidate() {
155  // Phpfastcache doesn't have invalidation as an action.
156  // This is handled during load
157  return true;
158  }
159 
163  public function purge() {
164  // Phpfastcache doesn't have purge as an action
165  return true;
166  }
167 
177  public function setNamespace($namespace = 'default') {
178  $this->namespace = $namespace;
179  }
180 
186  public function getNamespace() {
187  return $this->namespace;
188  }
189 
199  protected function prefixInstanceId(string $id): string {
200  return "{$this->getNamespace()}_{$id}";
201  }
202 
214  protected function sanitizeItemKey($key): string {
215  if (!is_string($key) && !is_int($key)) {
216  throw new InvalidArgumentException('key must be string or integer');
217  }
218 
219  return str_replace(['{', '}', '(', ')', '/', '\\', '@', ':'], '_', "{$key}");
220  }
221 
228  protected function createPool() {
229 
230  $drivers = [];
231  $drivers[] = $this->buildRedisDriver();
232  $drivers[] = $this->buildMemcachedDriver();
233  $drivers[] = $this->buildFileSystemDriver();
234  $drivers[] = $this->buildLocalFileSystemDriver();
235  $drivers[] = $this->buildBlackHoleDriver();
236  $drivers = array_filter($drivers);
237 
238  if (empty($drivers)) {
239  // the memory driver can only be used as a stand-alone driver (not combined in a cluster)
240  // other drivers already have a built in memory storage (default allowed in config)
241  $ephemeral = $this->buildEphemeralDriver();
242  if (!empty($ephemeral)) {
243  $drivers[] = $ephemeral;
244  }
245  }
246 
247  if (empty($drivers)) {
248  throw new ConfigurationException('Unable to initialize composite cache without drivers');
249  }
250 
251  if (count($drivers) === 1) {
252  return array_shift($drivers);
253  }
254 
255  $cluster = new ClusterAggregator($this->getNamespace());
256  foreach ($drivers as $driver) {
257  $cluster->aggregateDriver($driver);
258  }
259 
260  $cluster_driver = $cluster->getCluster();
261  $cluster_driver->setConfig(new ConfigurationOption([
262  'useStaticItemCaching' => true,
263  'itemDetailedDate' => true,
264  ]));
265 
266  return $cluster_driver;
267  }
268 
273  protected function buildRedisDriver() {
274  if (!($this->flags & ELGG_CACHE_PERSISTENT)) {
275  return null;
276  }
277 
278  if (!self::isRedisAvailable()) {
279  return null;
280  }
281 
282  $config = \Elgg\Cache\Config\Redis::fromElggConfig($this->namespace, $this->config);
283  if (empty($config)) {
284  return null;
285  }
286 
287  return CacheManager::getInstance('Redis', $config, $this->prefixInstanceId('redis'));
288  }
289 
294  protected function buildMemcachedDriver() {
295  if (!($this->flags & ELGG_CACHE_PERSISTENT)) {
296  return null;
297  }
298 
299  if (!self::isMemcacheAvailable()) {
300  return null;
301  }
302 
303  $config = \Elgg\Cache\Config\Memcached::fromElggConfig($this->namespace, $this->config);
304  if (empty($config)) {
305  return null;
306  }
307 
308  return CacheManager::getInstance('Memcached', $config, $this->prefixInstanceId('memcache'));
309  }
310 
315  protected function buildFileSystemDriver() {
316  if (!($this->flags & ELGG_CACHE_FILESYSTEM)) {
317  return null;
318  }
319 
320  $config = \Elgg\Cache\Config\Files::fromElggConfig($this->namespace, $this->config);
321  if (empty($config)) {
322  return null;
323  }
324 
325  try {
326  return CacheManager::getInstance('Files', $config, $this->prefixInstanceId('files'));
327  } catch (\Phpfastcache\Exceptions\PhpfastcacheIOException $e) {
328  if (!$this->config->installer_running) {
329  elgg_log($e, 'ERROR');
330  }
331  }
332 
333  return null;
334  }
335 
340  protected function buildLocalFileSystemDriver() {
341  if (!($this->flags & ELGG_CACHE_LOCALFILESYSTEM)) {
342  return null;
343  }
344 
345  $config = \Elgg\Cache\Config\LocalFiles::fromElggConfig($this->namespace, $this->config);
346  if (empty($config)) {
347  return null;
348  }
349 
350  try {
351  return CacheManager::getInstance('Files', $config, $this->prefixInstanceId('local_files'));
352  } catch (\Phpfastcache\Exceptions\PhpfastcacheIOException $e) {
353  if (!$this->config->installer_running) {
354  elgg_log($e, 'ERROR');
355  }
356  }
357 
358  return null;
359  }
360 
365  protected function buildEphemeralDriver() {
366  if (!($this->flags & ELGG_CACHE_RUNTIME)) {
367  return null;
368  }
369 
370  $config = new \Phpfastcache\Drivers\Memory\Config();
371 
372  $config->setUseStaticItemCaching(true);
373  $config->setItemDetailedDate(true);
374 
375  return CacheManager::getInstance('Memory', $config, $this->prefixInstanceId('memory'));
376  }
377 
382  protected function buildBlackHoleDriver() {
383  if (!($this->flags & ELGG_CACHE_BLACK_HOLE)) {
384  return null;
385  }
386 
387  $config = new \Phpfastcache\Drivers\Devnull\Config();
388 
389  return CacheManager::getInstance('Devnull', $config, $this->prefixInstanceId('devnull'));
390  }
391 
399  public static function isMemcacheAvailable(): bool {
400  return class_exists('Memcached');
401  }
402 
410  public static function isRedisAvailable(): bool {
411  return extension_loaded('Redis');
412  }
413 }
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
Extension of the DateTime class to support formatting a date using the locale.
Definition: DateTime.php:12
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.