Elgg  Version 4.3
CompositeCache.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Cache;
4 
5 use DateTime;
6 use ElggCache;
7 use Elgg\Config;
10 use Elgg\Values;
14 
20 class CompositeCache extends ElggCache {
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, $ttl = null) {
80  if ($this->disabled) {
81  return false;
82  }
83 
84  $ttl = $ttl ?: $this->ttl;
85 
86  $item = $this->pool->getItem($this->sanitizeItemKey($key));
87  $item->set($data);
88 
89  if (is_int($ttl)) {
90  $item->expiresAfter($ttl);
91  } elseif ($ttl instanceof DateTime) {
92  $item->expiresAt($ttl);
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  $item = $this->pool->getItem($this->sanitizeItemKey($key));
111  if (!$item->isHit()) {
112  return null;
113  }
114 
115  if ($this->validate_lastcache && $this->config->lastcache) {
116  $expiration_date = Values::normalizeTime($this->config->lastcache);
117 
118  if ($item->getCreationDate()->getTimestamp() < $expiration_date->getTimestamp()) {
119  $this->delete($key);
120  return null;
121  }
122  }
123 
124  return $item->get();
125  }
126 
131  public function delete($key) {
132  if ($this->disabled) {
133  return false;
134  }
135 
136  return $this->pool->deleteItem($this->sanitizeItemKey($key));
137  }
138 
143  public function clear() {
144  return $this->pool->clear();
145  }
146 
151  public function invalidate() {
152  // Phpfastcache doesn't have invalidation as an action.
153  // This is handled during load
154  return true;
155  }
156 
161  public function purge() {
162  // Phpfastcache doesn't have purge as an action
163  return true;
164  }
165 
175  public function setNamespace($namespace = 'default') {
176  $this->namespace = $namespace;
177  }
178 
184  public function getNamespace() {
185  return $this->namespace;
186  }
187 
197  protected function prefixInstanceId(string $id): string {
198  return "{$this->getNamespace()}_{$id}";
199  }
200 
212  protected function sanitizeItemKey($key): string {
213  if (!is_string($key) && !is_int($key)) {
214  throw new InvalidArgumentException('key must be string or integer');
215  }
216 
217  return str_replace(['{', '}', '(', ')', '/', '\\', '@', ':'], '_', "{$key}");
218  }
219 
226  protected function createPool() {
227 
228  $drivers = [];
229  $drivers[] = $this->buildApcDriver();
230  $drivers[] = $this->buildRedisDriver();
231  $drivers[] = $this->buildMemcachedDriver();
232  $drivers[] = $this->buildFileSystemDriver();
233  $drivers[] = $this->buildLocalFileSystemDriver();
234  $drivers[] = $this->buildBlackHoleDriver();
235  $drivers = array_filter($drivers);
236 
237  if (empty($drivers)) {
238  // the memory driver can only be used as a stand-alone driver (not combined in a cluster)
239  // other drivers already have a built in memory storage (default allowed in config)
240  $ephemeral = $this->buildEphemeralDriver();
241  if (!empty($ephemeral)) {
242  $drivers[] = $ephemeral;
243  }
244  }
245 
246  if (empty($drivers)) {
247  throw new ConfigurationException('Unable to initialize composite cache without drivers');
248  }
249 
250  if (count($drivers) === 1) {
251  return array_shift($drivers);
252  }
253 
254  $cluster = new ClusterAggregator($this->getNamespace());
255  foreach ($drivers as $driver) {
256  $cluster->aggregateDriver($driver);
257  }
258 
259  $cluster_driver = $cluster->getCluster();
260 
261  $cluster_driver->getConfig()->setPreventCacheSlams(true);
262  $cluster_driver->getConfig()->setDefaultChmod(0770);
263  $cluster_driver->getConfig()->setUseStaticItemCaching(true);
264  $cluster_driver->getConfig()->setItemDetailedDate(true);
265 
266  return $cluster_driver;
267  }
268 
273  protected function buildApcDriver() {
274  if (!($this->flags & ELGG_CACHE_APC)) {
275  return null;
276  }
277 
278  if (!extension_loaded('apc') || !ini_get('apc.enabled')) {
279  return null;
280  }
281 
282  elgg_deprecated_notice('The APC driver for caching is no longer available. Switch to an alternative method for caching.', '4.1');
283 
284  return null;
285  }
286 
291  protected function buildRedisDriver() {
292  if (!($this->flags & ELGG_CACHE_PERSISTENT)) {
293  return null;
294  }
295 
296  if (!self::isRedisAvailable()) {
297  return null;
298  }
299 
300  $config = \Elgg\Cache\Config\Redis::fromElggConfig($this->namespace, $this->config);
301  if (empty($config)) {
302  return null;
303  }
304 
305  return CacheManager::getInstance('Redis', $config, $this->prefixInstanceId('redis'));
306  }
307 
312  protected function buildMemcachedDriver() {
313  if (!($this->flags & ELGG_CACHE_PERSISTENT)) {
314  return null;
315  }
316 
317  if (!self::isMemcacheAvailable()) {
318  return null;
319  }
320 
321  $config = \Elgg\Cache\Config\Memcached::fromElggConfig($this->namespace, $this->config);
322  if (empty($config)) {
323  return null;
324  }
325 
326  return CacheManager::getInstance('Memcached', $config, $this->prefixInstanceId('memcache'));
327  }
328 
333  protected function buildFileSystemDriver() {
334  if (!($this->flags & ELGG_CACHE_FILESYSTEM)) {
335  return null;
336  }
337 
338  $config = \Elgg\Cache\Config\Files::fromElggConfig($this->namespace, $this->config);
339  if (empty($config)) {
340  return null;
341  }
342 
343  try {
344  return CacheManager::getInstance('Files', $config, $this->prefixInstanceId('files'));
345  } catch (\Phpfastcache\Exceptions\PhpfastcacheIOException $e) {
346  if (!$this->config->installer_running) {
347  elgg_log($e, 'ERROR');
348  }
349  }
350 
351  return null;
352  }
353 
358  protected function buildLocalFileSystemDriver() {
359  if (!($this->flags & ELGG_CACHE_LOCALFILESYSTEM)) {
360  return null;
361  }
362 
363  $config = \Elgg\Cache\Config\LocalFiles::fromElggConfig($this->namespace, $this->config);
364  if (empty($config)) {
365  return null;
366  }
367 
368  try {
369  return CacheManager::getInstance('Files', $config, $this->prefixInstanceId('local_files'));
370  } catch (\Phpfastcache\Exceptions\PhpfastcacheIOException $e) {
371  if (!$this->config->installer_running) {
372  elgg_log($e, 'ERROR');
373  }
374  }
375 
376  return null;
377  }
378 
383  protected function buildEphemeralDriver() {
384  if (!($this->flags & ELGG_CACHE_RUNTIME)) {
385  return null;
386  }
387 
388  $config = new \Phpfastcache\Drivers\Memstatic\Config();
389 
390  $config->setUseStaticItemCaching(true);
391  $config->setItemDetailedDate(true);
392 
393  return CacheManager::getInstance('Memstatic', $config, $this->prefixInstanceId('memstatic'));
394  }
395 
400  protected function buildBlackHoleDriver() {
401  if (!($this->flags & ELGG_CACHE_BLACK_HOLE)) {
402  return null;
403  }
404 
405  $config = new \Phpfastcache\Drivers\Devnull\Config();
406 
407  return CacheManager::getInstance('Devnull', $config, $this->prefixInstanceId('devnull'));
408  }
409 
417  public static function isMemcacheAvailable(): bool {
418  return class_exists('Memcached');
419  }
420 
428  public static function isRedisAvailable(): bool {
429  return extension_loaded('Redis');
430  }
431 }
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.
elgg_deprecated_notice(string $msg, string $dep_version)
Log a notice about deprecated use of a function, view, etc.
Definition: deprecation.php:52
getNamespace()
Get the namespace currently defined.
if(elgg_trigger_plugin_hook('usersettings:save', 'user', $hooks_params, true)) foreach($request->validation() ->all() as $item) $data
Definition: save.php:53
load($key)
Load data from the cache using a given key.
const ELGG_CACHE_BLACK_HOLE
Cache init values.
Definition: constants.php:136
prefixInstanceId(string $id)
Prefixes instance ids with namespace.
buildEphemeralDriver()
Builds in-memory driver.
setNamespace($namespace= 'default')
Set the namespace of this cache.
const ELGG_CACHE_APC
Definition: constants.php:140
Composite cache pool.
sanitizeItemKey($key)
Sanitizes item key for cache.
buildMemcachedDriver()
Builds Memcached driver.
const ELGG_CACHE_RUNTIME
Definition: constants.php:137
static getInstance(string $driver,?ConfigurationOptionInterface $config=null,?string $instanceId=null)
static fromElggConfig(string $namespace,\Elgg\Config $config)
Factory to return a config object to be used when starting a driver.
Definition: LocalFiles.php:23
buildApcDriver()
Builds APC driver.
elgg_log($message, $level=\Psr\Log\LogLevel::NOTICE)
Log a message.
Definition: elgglib.php:399
save($key, $data, $ttl=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: Files.php:23
if($container instanceof ElggGroup &&$container->guid!=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
const ELGG_CACHE_LOCALFILESYSTEM
Definition: constants.php:141
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
createPool()
Create a new cluster/pool of drivers.
const ELGG_CACHE_PERSISTENT
Definition: constants.php:139
const ELGG_CACHE_FILESYSTEM
Definition: constants.php:138
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:76
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.