forked from leftypol/leftypol
CacheDriver: moved to subdirectory
This commit is contained in:
parent
8ee471c868
commit
311a5477f8
8 changed files with 8 additions and 8 deletions
28
inc/Data/Driver/Cache/ApcuCacheDriver.php
Normal file
28
inc/Data/Driver/Cache/ApcuCacheDriver.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver\Cache;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
class ApcuCacheDriver implements CacheDriver {
|
||||
public function get(string $key): mixed {
|
||||
$success = false;
|
||||
$ret = \apcu_fetch($key, $success);
|
||||
if ($success === false) {
|
||||
return null;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, mixed $expires = false): void {
|
||||
\apcu_store($key, $value, (int)$expires);
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
\apcu_delete($key);
|
||||
}
|
||||
|
||||
public function flush(): void {
|
||||
\apcu_clear_cache();
|
||||
}
|
||||
}
|
||||
28
inc/Data/Driver/Cache/ArrayCacheDriver.php
Normal file
28
inc/Data/Driver/Cache/ArrayCacheDriver.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver\Cache;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
/**
|
||||
* A simple process-wide PHP array.
|
||||
*/
|
||||
class ArrayCacheDriver implements CacheDriver {
|
||||
private static array $inner = [];
|
||||
|
||||
public function get(string $key): mixed {
|
||||
return isset(self::$inner[$key]) ? self::$inner[$key] : null;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, mixed $expires = false): void {
|
||||
self::$inner[$key] = $value;
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
unset(self::$inner[$key]);
|
||||
}
|
||||
|
||||
public function flush(): void {
|
||||
self::$inner = [];
|
||||
}
|
||||
}
|
||||
38
inc/Data/Driver/Cache/CacheDriver.php
Normal file
38
inc/Data/Driver/Cache/CacheDriver.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver\Cache;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
interface CacheDriver {
|
||||
/**
|
||||
* Get the value of associated with the key.
|
||||
*
|
||||
* @param string $key The key of the value.
|
||||
* @return mixed|null The value associated with the key, or null if there is none.
|
||||
*/
|
||||
public function get(string $key): mixed;
|
||||
|
||||
/**
|
||||
* Set a key-value pair.
|
||||
*
|
||||
* @param string $key The key.
|
||||
* @param mixed $value The value.
|
||||
* @param int|false $expires After how many seconds the pair will expire. Use false or ignore this parameter to keep
|
||||
* the value until it gets evicted to make space for more items. Some drivers will always
|
||||
* ignore this parameter and store the pair until it's removed.
|
||||
*/
|
||||
public function set(string $key, mixed $value, mixed $expires = false): void;
|
||||
|
||||
/**
|
||||
* Delete a key-value pair.
|
||||
*
|
||||
* @param string $key The key.
|
||||
*/
|
||||
public function delete(string $key): void;
|
||||
|
||||
/**
|
||||
* Delete all the key-value pairs.
|
||||
*/
|
||||
public function flush(): void;
|
||||
}
|
||||
20
inc/Data/Driver/Cache/CacheDriverTrait.php
Normal file
20
inc/Data/Driver/Cache/CacheDriverTrait.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver\Cache;
|
||||
|
||||
|
||||
trait CacheDriverTrait {
|
||||
/**
|
||||
* Tries to interpret the uri as a path to a unix socket.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return ?string The path to the socket, null if it cannot be interpreted as such.
|
||||
*/
|
||||
private static function asUnixSocketPath(string $uri): ?string {
|
||||
if (str_starts_with($uri, 'unix:')) {
|
||||
return \substr($uri, 5);
|
||||
} elseif (str_starts_with($uri, ':')) {
|
||||
return \substr($uri, 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
155
inc/Data/Driver/Cache/FsCachedriver.php
Normal file
155
inc/Data/Driver/Cache/FsCachedriver.php
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver\Cache;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
class FsCacheDriver implements CacheDriver {
|
||||
private string $prefix;
|
||||
private string $base_path;
|
||||
private mixed $lock_fd;
|
||||
private int|false $collect_chance_den;
|
||||
|
||||
|
||||
private function prepareKey(string $key): string {
|
||||
$key = \str_replace('/', '::', $key);
|
||||
$key = \str_replace("\0", '', $key);
|
||||
return $this->prefix . $key;
|
||||
}
|
||||
|
||||
private function sharedLockCache(): void {
|
||||
\flock($this->lock_fd, LOCK_SH);
|
||||
}
|
||||
|
||||
private function exclusiveLockCache(): void {
|
||||
\flock($this->lock_fd, LOCK_EX);
|
||||
}
|
||||
|
||||
private function unlockCache(): void {
|
||||
\flock($this->lock_fd, LOCK_UN);
|
||||
}
|
||||
|
||||
private function collectImpl(): int {
|
||||
/*
|
||||
* A read lock is ok, since it's alright if we delete expired items from under the feet of other processes, and
|
||||
* no other process add new cache items or refresh existing ones.
|
||||
*/
|
||||
$files = \glob($this->base_path . $this->prefix . '*', \GLOB_NOSORT);
|
||||
$count = 0;
|
||||
foreach ($files as $file) {
|
||||
$data = \file_get_contents($file);
|
||||
$wrapped = \json_decode($data, true, 512, \JSON_THROW_ON_ERROR);
|
||||
if ($wrapped['expires'] !== false && $wrapped['expires'] <= \time()) {
|
||||
if (@\unlink($file)) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function maybeCollect(): void {
|
||||
if ($this->collect_chance_den !== false && \mt_rand(0, $this->collect_chance_den - 1) === 0) {
|
||||
$this->collect_chance_den = false; // Collect only once per instance (aka process).
|
||||
$this->collectImpl();
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct(string $prefix, string $base_path, string $lock_file, int|false $collect_chance_den) {
|
||||
if ($base_path[\strlen($base_path) - 1] !== '/') {
|
||||
$base_path = "$base_path/";
|
||||
}
|
||||
|
||||
if (!\is_dir($base_path)) {
|
||||
throw new \RuntimeException("$base_path is not a directory!");
|
||||
}
|
||||
|
||||
if (!\is_writable($base_path)) {
|
||||
throw new \RuntimeException("$base_path is not writable!");
|
||||
}
|
||||
|
||||
$this->lock_fd = \fopen($base_path . $lock_file, 'w');
|
||||
if ($this->lock_fd === false) {
|
||||
throw new \RuntimeException('Unable to open the lock file!');
|
||||
}
|
||||
|
||||
$this->prefix = $prefix;
|
||||
$this->base_path = $base_path;
|
||||
$this->collect_chance_den = $collect_chance_den;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function get(string $key): mixed {
|
||||
$key = $this->prepareKey($key);
|
||||
|
||||
$this->sharedLockCache();
|
||||
|
||||
// Collect expired items first so if the target key is expired we shortcut to failure in the next lines.
|
||||
$this->maybeCollect();
|
||||
|
||||
$fd = \fopen($this->base_path . $key, 'r');
|
||||
if ($fd === false) {
|
||||
$this->unlockCache();
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = \stream_get_contents($fd);
|
||||
\fclose($fd);
|
||||
$this->unlockCache();
|
||||
$wrapped = \json_decode($data, true, 512, \JSON_THROW_ON_ERROR);
|
||||
|
||||
if ($wrapped['expires'] !== false && $wrapped['expires'] <= \time()) {
|
||||
// Already expired, leave it there since we already released the lock and pretend it doesn't exist.
|
||||
return null;
|
||||
} else {
|
||||
return $wrapped['inner'];
|
||||
}
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, mixed $expires = false): void {
|
||||
$key = $this->prepareKey($key);
|
||||
|
||||
$wrapped = [
|
||||
'expires' => $expires ? \time() + $expires : false,
|
||||
'inner' => $value
|
||||
];
|
||||
|
||||
$data = \json_encode($wrapped);
|
||||
$this->exclusiveLockCache();
|
||||
$this->maybeCollect();
|
||||
\file_put_contents($this->base_path . $key, $data);
|
||||
$this->unlockCache();
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
$key = $this->prepareKey($key);
|
||||
|
||||
$this->exclusiveLockCache();
|
||||
@\unlink($this->base_path . $key);
|
||||
$this->maybeCollect();
|
||||
$this->unlockCache();
|
||||
}
|
||||
|
||||
public function collect(): int {
|
||||
$this->sharedLockCache();
|
||||
$count = $this->collectImpl();
|
||||
$this->unlockCache();
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function flush(): void {
|
||||
$this->exclusiveLockCache();
|
||||
$files = \glob($this->base_path . $this->prefix . '*', \GLOB_NOSORT);
|
||||
foreach ($files as $file) {
|
||||
@\unlink($file);
|
||||
}
|
||||
$this->unlockCache();
|
||||
}
|
||||
|
||||
public function close(): void {
|
||||
\fclose($this->lock_fd);
|
||||
}
|
||||
}
|
||||
74
inc/Data/Driver/Cache/MemcacheCacheDriver.php
Normal file
74
inc/Data/Driver/Cache/MemcacheCacheDriver.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver\Cache;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
class MemcachedCacheDriver implements CacheDriver {
|
||||
use CacheDriverTrait;
|
||||
|
||||
private \Memcached $inner;
|
||||
|
||||
|
||||
public function __construct(string $prefix, string $server_uri, int $server_port, int $server_weight) {
|
||||
$this->inner = new \Memcached();
|
||||
if (!$this->inner->setOption(\Memcached::OPT_BINARY_PROTOCOL, true)) {
|
||||
$err = $this->inner->getResultMessage();
|
||||
throw new \RuntimeException("Unable to set the memcached protocol: '$err'");
|
||||
}
|
||||
if (!$this->inner->setOption(\Memcached::OPT_PREFIX_KEY, $prefix)) {
|
||||
$err = $this->inner->getResultMessage();
|
||||
throw new \RuntimeException("Unable to set the memcached prefix: '$err'");
|
||||
}
|
||||
|
||||
$maybe_unix_path = self::asUnixSocketPath($server_uri);
|
||||
$is_unix = $maybe_unix_path !== null;
|
||||
if ($is_unix) {
|
||||
$server_uri = $maybe_unix_path;
|
||||
}
|
||||
|
||||
// Memcached keeps the server connections open across requests.
|
||||
$current_servers = $this->inner->getServerList();
|
||||
$found_in_curr = false;
|
||||
foreach ($current_servers as $curr) {
|
||||
// Ignore the port if the server is connected with a unix socket.
|
||||
if ($curr['host'] === $server_uri && ($is_unix || $curr['port'] === $server_port)) {
|
||||
$found_in_curr = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found_in_curr) {
|
||||
if (!empty($current_servers)) {
|
||||
if (!$this->inner->resetServerList()) {
|
||||
$err = $this->inner->getResultMessage();
|
||||
throw new \RuntimeException("Unable to reset the memcached server list: '$err'");
|
||||
}
|
||||
}
|
||||
if (!$this->inner->addServer($server_uri, $server_port, $server_weight)) {
|
||||
$err = $this->inner->getResultMessage();
|
||||
throw new \RuntimeException("Unable to add memcached servers: '$err'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get(string $key): mixed {
|
||||
$ret = $this->inner->get($key);
|
||||
// If the returned value is false but the retrival was a success, then the value stored was a boolean false.
|
||||
if ($ret === false && $this->inner->getResultCode() !== \Memcached::RES_SUCCESS) {
|
||||
return null;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, mixed $expires = false): void {
|
||||
$this->inner->set($key, $value, (int)$expires);
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
$this->inner->delete($key);
|
||||
}
|
||||
|
||||
public function flush(): void {
|
||||
$this->inner->flush();
|
||||
}
|
||||
}
|
||||
26
inc/Data/Driver/Cache/NoneCacheDriver.php
Normal file
26
inc/Data/Driver/Cache/NoneCacheDriver.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver\Cache;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
/**
|
||||
* No-op cache. Useful for testing.
|
||||
*/
|
||||
class NoneCacheDriver implements CacheDriver {
|
||||
public function get(string $key): mixed {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, mixed $expires = false): void {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
public function flush(): void {
|
||||
// No-op.
|
||||
}
|
||||
}
|
||||
69
inc/Data/Driver/Cache/RedisCacheDriver.php
Normal file
69
inc/Data/Driver/Cache/RedisCacheDriver.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver\Cache;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
class RedisCacheDriver implements CacheDriver {
|
||||
use CacheDriverTrait;
|
||||
|
||||
private string $prefix;
|
||||
private \Redis $inner;
|
||||
|
||||
public function __construct(string $prefix, string $host, ?int $port, ?string $password, int $database) {
|
||||
$this->inner = new \Redis();
|
||||
$maybe_unix = self::asUnixSocketPath($host);
|
||||
|
||||
if ($maybe_unix !== null) {
|
||||
$this->inner->connect($maybe_unix);
|
||||
} elseif ($port === null) {
|
||||
$this->inner->connect($host);
|
||||
} else {
|
||||
// IP + port.
|
||||
$this->inner->connect($host, $port);
|
||||
}
|
||||
if ($password) {
|
||||
$this->inner->auth($password);
|
||||
}
|
||||
if (!$this->inner->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_JSON)) {
|
||||
throw new \RuntimeException('Unable to configure Redis serializer');
|
||||
}
|
||||
if (!$this->inner->select($database)) {
|
||||
throw new \RuntimeException('Unable to connect to Redis database!');
|
||||
}
|
||||
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
public function get(string $key): mixed {
|
||||
$ret = $this->inner->get($this->prefix . $key);
|
||||
if ($ret === false) {
|
||||
return null;
|
||||
}
|
||||
if ($ret === null) {
|
||||
return false;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, mixed $expires = false): void {
|
||||
$value = $value === false ? null : $value;
|
||||
if ($expires === false) {
|
||||
$this->inner->set($this->prefix . $key, $value);
|
||||
} else {
|
||||
$this->inner->setEx($this->prefix . $key, $expires, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
$this->inner->del($this->prefix . $key);
|
||||
}
|
||||
|
||||
public function flush(): void {
|
||||
if (empty($this->prefix)) {
|
||||
$this->inner->flushDB();
|
||||
} else {
|
||||
$this->inner->unlink($this->inner->keys("{$this->prefix}*"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue