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;
14 
20 class CompositeCache extends BaseCache {
21 
22  public const CACHE_BLACK_HOLE = 1;
23  public const CACHE_RUNTIME = 2;
24  public const CACHE_FILESYSTEM = 4;
25  public const CACHE_PERSISTENT = 8;
26  public const CACHE_LOCALFILESYSTEM = 32;
27 
31  protected int $ttl = 86400;
32 
36  protected $pool;
37 
46  public function __construct(protected string $namespace, protected Config $config, protected int $flags, protected bool $validate_lastcache = true) {
47  $this->pool = $this->createPool();
48  }
49 
59  public function save($key, $data, $expire_after = null) {
60  if ($this->disabled) {
61  return false;
62  }
63 
64  $expire_after = $expire_after ?: $this->ttl;
65 
66  $item = $this->pool->getItem($this->sanitizeItemKey($key));
67  $item->set($data);
68 
69  if (is_int($expire_after)) {
70  $item->expiresAfter($expire_after);
71  } elseif ($expire_after instanceof \DateTime) {
72  $item->expiresAt($expire_after);
73  }
74 
75  return $this->pool->save($item);
76  }
77 
85  public function load($key) {
86  if ($this->disabled) {
87  return null;
88  }
89 
90  try {
91  $item = $this->pool->getItem($this->sanitizeItemKey($key));
92  if (!$item->isHit()) {
93  return null;
94  }
95 
96  if ($this->validate_lastcache && $this->config->lastcache) {
97  $expiration_date = Values::normalizeTime($this->config->lastcache);
98 
99  if ($item->getCreationDate()->getTimestamp() < $expiration_date->getTimestamp()) {
100  $this->delete($key);
101  return null;
102  }
103  }
104 
105  return $item->get();
106  } catch (PhpfastcacheRootException $e) {
107  // something wrong with the cache
108  elgg_log($e->getMessage(), 'ERROR');
109  return null;
110  }
111  }
112 
116  public function delete($key) {
117  if ($this->disabled) {
118  return false;
119  }
120 
121  return $this->pool->deleteItem($this->sanitizeItemKey($key));
122  }
123 
127  public function clear() {
128  return $this->pool->clear();
129  }
130 
134  public function invalidate() {
135  // Phpfastcache doesn't have invalidation as an action.
136  // This is handled during load
137  return true;
138  }
139 
143  public function purge() {
144  // Phpfastcache doesn't have purge as an action
145  return true;
146  }
147 
157  public function setNamespace($namespace = 'default'): void {
158  $this->namespace = $namespace;
159  }
160 
166  public function getNamespace(): string {
167  return $this->namespace;
168  }
169 
179  protected function prefixInstanceId(string $id): string {
180  return "{$this->getNamespace()}_{$id}";
181  }
182 
194  protected function sanitizeItemKey($key): string {
195  if (!is_string($key) && !is_int($key)) {
196  throw new InvalidArgumentException('key must be string or integer');
197  }
198 
199  return str_replace(['{', '}', '(', ')', '/', '\\', '@', ':'], '_', "{$key}");
200  }
201 
208  protected function createPool(): ExtendedCacheItemPoolInterface {
209 
210  $drivers = [];
211  $drivers[] = $this->buildRedisDriver();
212  $drivers[] = $this->buildMemcachedDriver();
213  $drivers[] = $this->buildFileSystemDriver();
214  $drivers[] = $this->buildLocalFileSystemDriver();
215  $drivers[] = $this->buildBlackHoleDriver();
216  $drivers = array_filter($drivers);
217 
218  if (empty($drivers)) {
219  // the memory driver can only be used as a stand-alone driver (not combined in a cluster)
220  // other drivers already have a built in memory storage (default allowed in config)
221  $ephemeral = $this->buildEphemeralDriver();
222  if (!empty($ephemeral)) {
223  $drivers[] = $ephemeral;
224  }
225  }
226 
227  if (empty($drivers)) {
228  throw new ConfigurationException('Unable to initialize composite cache without drivers');
229  }
230 
231  if (count($drivers) === 1) {
232  return array_shift($drivers);
233  }
234 
235  $cluster = new ClusterAggregator($this->getNamespace());
236  foreach ($drivers as $driver) {
237  $cluster->aggregateDriver($driver);
238  }
239 
240  $cluster_driver = $cluster->getCluster();
241  $cluster_driver->setConfig(new ConfigurationOption([
242  'useStaticItemCaching' => true,
243  'itemDetailedDate' => true,
244  ]));
245 
246  return $cluster_driver;
247  }
248 
253  protected function buildRedisDriver(): ?ExtendedCacheItemPoolInterface {
254  if (!($this->flags & self::CACHE_PERSISTENT)) {
255  return null;
256  }
257 
258  if (!self::isRedisAvailable()) {
259  return null;
260  }
261 
262  $config = \Elgg\Cache\Config\Redis::fromElggConfig($this->namespace, $this->config);
263  if (empty($config)) {
264  return null;
265  }
266 
267  return CacheManager::getInstance('Redis', $config, $this->prefixInstanceId('redis'));
268  }
269 
274  protected function buildMemcachedDriver(): ?ExtendedCacheItemPoolInterface {
275  if (!($this->flags & self::CACHE_PERSISTENT)) {
276  return null;
277  }
278 
279  if (!self::isMemcacheAvailable()) {
280  return null;
281  }
282 
283  $config = \Elgg\Cache\Config\Memcached::fromElggConfig($this->namespace, $this->config);
284  if (empty($config)) {
285  return null;
286  }
287 
288  return CacheManager::getInstance('Memcached', $config, $this->prefixInstanceId('memcache'));
289  }
290 
295  protected function buildFileSystemDriver(): ?ExtendedCacheItemPoolInterface {
296  if (!($this->flags & self::CACHE_FILESYSTEM)) {
297  return null;
298  }
299 
300  $config = \Elgg\Cache\Config\Files::fromElggConfig($this->namespace, $this->config);
301  if (empty($config)) {
302  return null;
303  }
304 
305  try {
306  return CacheManager::getInstance('Files', $config, $this->prefixInstanceId('files'));
307  } catch (\Phpfastcache\Exceptions\PhpfastcacheIOException $e) {
308  if (!$this->config->installer_running) {
309  elgg_log($e, \Psr\Log\LogLevel::ERROR);
310  }
311  }
312 
313  return null;
314  }
315 
320  protected function buildLocalFileSystemDriver(): ?ExtendedCacheItemPoolInterface {
321  if (!($this->flags & self::CACHE_LOCALFILESYSTEM)) {
322  return null;
323  }
324 
325  $config = \Elgg\Cache\Config\LocalFiles::fromElggConfig($this->namespace, $this->config);
326  if (empty($config)) {
327  return null;
328  }
329 
330  try {
331  return CacheManager::getInstance('Files', $config, $this->prefixInstanceId('local_files'));
332  } catch (\Phpfastcache\Exceptions\PhpfastcacheIOException $e) {
333  if (!$this->config->installer_running) {
334  elgg_log($e, \Psr\Log\LogLevel::ERROR);
335  }
336  }
337 
338  return null;
339  }
340 
345  protected function buildEphemeralDriver(): ?ExtendedCacheItemPoolInterface {
346  if (!($this->flags & self::CACHE_RUNTIME)) {
347  return null;
348  }
349 
350  $config = new \Phpfastcache\Drivers\Memory\Config();
351 
352  $config->setUseStaticItemCaching(true);
353  $config->setItemDetailedDate(true);
354 
355  return CacheManager::getInstance('Memory', $config, $this->prefixInstanceId('memory'));
356  }
357 
362  protected function buildBlackHoleDriver(): ?ExtendedCacheItemPoolInterface {
363  if (!($this->flags & self::CACHE_BLACK_HOLE)) {
364  return null;
365  }
366 
367  $config = new \Phpfastcache\Drivers\Devnull\Config();
368 
369  return CacheManager::getInstance('Devnull', $config, $this->prefixInstanceId('devnull'));
370  }
371 
379  public static function isMemcacheAvailable(): bool {
380  return class_exists('Memcached');
381  }
382 
390  public static function isRedisAvailable(): bool {
391  return extension_loaded('Redis');
392  }
393 }
if(!$items) $item
Definition: delete.php:13
static isMemcacheAvailable()
Helper function to check if memcache is available.
int $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.
__construct(protected string $namespace, protected Config $config, protected int $flags, protected bool $validate_lastcache=true)
Constructor.
getNamespace()
Get the namespace currently defined.
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is void
Definition: LICENSE.txt:215
load($key)
Load data from the cache using a given key.
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.
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
Composite cache pool.
sanitizeItemKey($key)
Sanitizes item key for cache.
if(!$entity instanceof\ElggUser) $data
Definition: attributes.php:13
buildMemcachedDriver()
Builds Memcached driver.
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
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.
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