Added cache wrappers.
This commit is contained in:
parent
09fc6b3958
commit
ad75d72aa0
19 changed files with 1258 additions and 9 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.2404.21726
|
||||
0.2404.102223
|
||||
|
|
12
composer.lock
generated
12
composer.lock
generated
|
@ -627,16 +627,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "10.5.16",
|
||||
"version": "10.5.17",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "18f8d4a5f52b61fdd9370aaae3167daa0eeb69cd"
|
||||
"reference": "c1f736a473d21957ead7e94fcc029f571895abf5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/18f8d4a5f52b61fdd9370aaae3167daa0eeb69cd",
|
||||
"reference": "18f8d4a5f52b61fdd9370aaae3167daa0eeb69cd",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c1f736a473d21957ead7e94fcc029f571895abf5",
|
||||
"reference": "c1f736a473d21957ead7e94fcc029f571895abf5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -708,7 +708,7 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.16"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.17"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -724,7 +724,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-28T10:08:10+00:00"
|
||||
"time": "2024-04-05T04:39:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
|
38
src/Cache/ArrayCache/ArrayCacheBackend.php
Normal file
38
src/Cache/ArrayCache/ArrayCacheBackend.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
// ArrayCacheBackend.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\ArrayCache;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Index\Cache\{ICacheBackend,ICacheProvider,ICacheProviderInfo};
|
||||
|
||||
/**
|
||||
* Information about the dummy cache backend.
|
||||
*/
|
||||
class ArrayCacheBackend implements ICacheBackend {
|
||||
public function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dummy cache provider.
|
||||
*
|
||||
* @param ArrayCacheProviderInfo $providerInfo Dummy provider info.
|
||||
* @return ArrayCacheProvider Dummy provider instance.
|
||||
*/
|
||||
public function createProvider(ICacheProviderInfo $providerInfo): ICacheProvider {
|
||||
if(!($providerInfo instanceof ArrayCacheProviderInfo))
|
||||
throw new InvalidArgumentException('$providerInfo must by of type ArrayCacheProviderInfo');
|
||||
|
||||
return new ArrayCacheProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCacheProviderInfo Dummy provider info instance.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): ICacheProviderInfo {
|
||||
return new ArrayCacheProviderInfo;
|
||||
}
|
||||
}
|
75
src/Cache/ArrayCache/ArrayCacheProvider.php
Normal file
75
src/Cache/ArrayCache/ArrayCacheProvider.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
// ArrayCacheProvider.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\ArrayCache;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Index\Cache\ICacheProvider;
|
||||
|
||||
/**
|
||||
* Represents a dummy cache provider.
|
||||
*/
|
||||
class ArrayCacheProvider implements ICacheProvider {
|
||||
private array $items = [];
|
||||
|
||||
public function get(string $key): mixed {
|
||||
if(!array_key_exists($key, $this->items))
|
||||
return null;
|
||||
|
||||
$item = $this->items[$key];
|
||||
if($item['ttl'] > 0 && $item['ttl'] <= time()) {
|
||||
unset($this->items[$key]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return unserialize($item['value']);
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, int $ttl = 0): void {
|
||||
if($ttl < 0)
|
||||
throw new InvalidArgumentException('$ttl must be equal to or greater than 0');
|
||||
|
||||
$this->items[$key] = [
|
||||
'value' => serialize($value),
|
||||
'ttl' => $ttl < 1 ? 0 : time() + $ttl,
|
||||
];
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
if(array_key_exists($key, $this->items))
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
|
||||
public function touch(string $key, int $ttl = 0): void {
|
||||
if($ttl < 0)
|
||||
throw new InvalidArgumentException('$ttl must be equal to or greater than 0');
|
||||
|
||||
if(array_key_exists($key, $this->items))
|
||||
$this->items[$key]['ttl'] = $ttl < 1 ? 0 : time() + $ttl;
|
||||
}
|
||||
|
||||
public function increment(string $key, int $amount = 1): int {
|
||||
$exists = array_key_exists($key, $this->items);
|
||||
$value = $exists ? unserialize($this->items[$key]['value']) : 0;
|
||||
$value += $amount;
|
||||
$serialised = serialize($value);
|
||||
|
||||
if($exists)
|
||||
$this->items[$key]['value'] = $serialised;
|
||||
else
|
||||
$this->items[$key] = [
|
||||
'value' => $serialised,
|
||||
'ttl' => 0,
|
||||
];
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function decrement(string $key, int $amount = 1): int {
|
||||
return $this->increment($key, $amount * -1);
|
||||
}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
13
src/Cache/ArrayCache/ArrayCacheProviderInfo.php
Normal file
13
src/Cache/ArrayCache/ArrayCacheProviderInfo.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
// ArrayCacheProviderInfo.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\ArrayCache;
|
||||
|
||||
use Index\Cache\ICacheProviderInfo;
|
||||
|
||||
/**
|
||||
* Represents dummy provider info.
|
||||
*/
|
||||
class ArrayCacheProviderInfo implements ICacheProviderInfo {}
|
77
src/Cache/CacheTools.php
Normal file
77
src/Cache/CacheTools.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
// CacheTools.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Common cache actions.
|
||||
*/
|
||||
final class CacheTools {
|
||||
private const CACHE_PROTOS = [
|
||||
'null' => ArrayCache\ArrayCacheBackend::class,
|
||||
'array' => ArrayCache\ArrayCacheBackend::class,
|
||||
'memcache' => Memcached\MemcachedBackend::class,
|
||||
'memcached' => Memcached\MemcachedBackend::class,
|
||||
'redis' => Valkey\ValkeyBackend::class,
|
||||
'keydb' => Valkey\ValkeyBackend::class,
|
||||
'valkey' => Valkey\ValkeyBackend::class,
|
||||
];
|
||||
|
||||
private static function parseDsnUri(string $dsn): array {
|
||||
$uri = parse_url($dsn);
|
||||
if($uri === false)
|
||||
throw new InvalidArgumentException('$dsn is not a valid uri.');
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private static function resolveBackend(array $uri): ICacheBackend {
|
||||
static $backends = [];
|
||||
|
||||
$scheme = $uri['scheme'];
|
||||
|
||||
if(in_array($scheme, $backends))
|
||||
$backend = $backends[$scheme];
|
||||
else {
|
||||
$backend = null;
|
||||
|
||||
if(array_key_exists($scheme, self::CACHE_PROTOS))
|
||||
$name = self::CACHE_PROTOS[$scheme];
|
||||
else
|
||||
$name = str_replace('-', '\\', $scheme);
|
||||
|
||||
if(class_exists($name) && is_subclass_of($name, ICacheBackend::class)) {
|
||||
$backend = new $name;
|
||||
$name = get_class($backend);
|
||||
}
|
||||
|
||||
if($backend === null)
|
||||
throw new RuntimeException('No implementation is available for the specified scheme.');
|
||||
if(!$backend->isAvailable())
|
||||
throw new RuntimeException('Requested cache backend is not available, likely due to missing dependencies.');
|
||||
|
||||
$backends[$name] = $backend;
|
||||
}
|
||||
|
||||
return $backend;
|
||||
}
|
||||
|
||||
public static function backend(string $dsn): ICacheBackend {
|
||||
return self::resolveBackend(self::parseDsnUri($dsn));
|
||||
}
|
||||
|
||||
public static function parse(string $dsn): ICacheProviderInfo {
|
||||
$uri = self::parseDsnUri($dsn);
|
||||
return self::resolveBackend($uri)->parseDsn($uri);
|
||||
}
|
||||
|
||||
public static function create(string $dsn): ICacheProvider {
|
||||
$uri = self::parseDsnUri($dsn);
|
||||
$backend = self::resolveBackend($uri);
|
||||
return $backend->createProvider($backend->parseDsn($uri));
|
||||
}
|
||||
}
|
36
src/Cache/ICacheBackend.php
Normal file
36
src/Cache/ICacheBackend.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
// ICacheBackend.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache;
|
||||
|
||||
/**
|
||||
* Information about a cache provider. Should not have any external dependencies.
|
||||
*/
|
||||
interface ICacheBackend {
|
||||
/**
|
||||
* Checks whether the driver is available and a provider can be made.
|
||||
*
|
||||
* @return bool If true a provider can be made, if false a required extension is missing.
|
||||
*/
|
||||
function isAvailable(): bool;
|
||||
|
||||
/**
|
||||
* Creates the cache provider described in the argument.
|
||||
*
|
||||
* @param ICacheProviderInfo $providerInfo Object that describes the desired provider.
|
||||
* @throws \InvalidArgumentException An invalid implementation of ICacheProviderInfo was provided.
|
||||
* @throws \RuntimeException If you ignored the output of isAvailable and tried to create an instance anyway.
|
||||
* @return ICacheProvider The provider described in the provider info.
|
||||
*/
|
||||
function createProvider(ICacheProviderInfo $providerInfo): ICacheProvider;
|
||||
|
||||
/**
|
||||
* Constructs a cache info instance from a dsn.
|
||||
*
|
||||
* @param string|array $dsn DSN with provider information.
|
||||
* @return ICacheProviderInfo Provider info based on the dsn.
|
||||
*/
|
||||
function parseDsn(string|array $dsn): ICacheProviderInfo;
|
||||
}
|
70
src/Cache/ICacheProvider.php
Normal file
70
src/Cache/ICacheProvider.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
// ICacheProvider.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache;
|
||||
|
||||
use Index\ICloseable;
|
||||
|
||||
/**
|
||||
* Represents a cache provider.
|
||||
*/
|
||||
interface ICacheProvider extends ICloseable {
|
||||
/**
|
||||
* Retrieve an item from the cache.
|
||||
*
|
||||
* @param string $key Key under which the value is stored.
|
||||
* @return mixed value stored in the cache or null otherwise.
|
||||
*/
|
||||
function get(string $key): mixed;
|
||||
|
||||
/**
|
||||
* Store an item in the cache.
|
||||
*
|
||||
* @param string $key Key under which to store the value.
|
||||
* @param mixed $value Value to store.
|
||||
* @param int $ttl Amount of seconds to store the value for, 0 for indefinite.
|
||||
* @throws \RuntimeException if storing the value in the cache failed.
|
||||
* @throws \InvalidArgumentException if an argument is incorrectly formatted, e.g. $ttl set to a negative value.
|
||||
*/
|
||||
function set(string $key, mixed $value, int $ttl = 0): void;
|
||||
|
||||
/**
|
||||
* Deletes an item from the cache.
|
||||
*
|
||||
* @param string $key Key to be deleted.
|
||||
* @throws \RuntimeException if an error occurred during deletion.
|
||||
*/
|
||||
function delete(string $key): void;
|
||||
|
||||
/**
|
||||
* Sets a new expiration time on an item.
|
||||
*
|
||||
* @param string $key Key to be touched.
|
||||
* @param int $ttl Amount of seconds to store the value for, 0 for indefinite.
|
||||
* @throws \RuntimeException if storing the value in the cache failed.
|
||||
* @throws \InvalidArgumentException if an argument is incorrectly formatted, e.g. $ttl set to a negative value.
|
||||
*/
|
||||
function touch(string $key, int $ttl = 0): void;
|
||||
|
||||
/**
|
||||
* Increments an item.
|
||||
*
|
||||
* @param string $key Key to be incremented.
|
||||
* @param int $amount Amount to increment by. Defaults to 1.
|
||||
* @throws \RuntimeException if storing the value in the cache failed.
|
||||
* @return int new, incremented value.
|
||||
*/
|
||||
function increment(string $key, int $amount = 1): int;
|
||||
|
||||
/**
|
||||
* Decrements an item.
|
||||
*
|
||||
* @param string $key Key to be decremented.
|
||||
* @param int $amount Amount to decrement by. Defaults to 1.
|
||||
* @throws \RuntimeException if storing the value in the cache failed.
|
||||
* @return int new, decremented value.
|
||||
*/
|
||||
function decrement(string $key, int $amount = 1): int;
|
||||
}
|
13
src/Cache/ICacheProviderInfo.php
Normal file
13
src/Cache/ICacheProviderInfo.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
// ICacheProviderInfo.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache;
|
||||
|
||||
/**
|
||||
* Base type for cache provider info.
|
||||
*
|
||||
* Any cache backend should have its own implementation of this, there are no baseline requirements.
|
||||
*/
|
||||
interface ICacheProviderInfo {}
|
108
src/Cache/Memcached/MemcachedBackend.php
Normal file
108
src/Cache/Memcached/MemcachedBackend.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
// MemcachedBackend.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\Memcached;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Cache\{ICacheBackend,ICacheProvider,ICacheProviderInfo};
|
||||
use Index\Net\{EndPoint,UnixEndPoint};
|
||||
|
||||
/**
|
||||
* Information about the memcached backend.
|
||||
*/
|
||||
class MemcachedBackend implements ICacheBackend {
|
||||
public function isAvailable(): bool {
|
||||
return extension_loaded('memcached')
|
||||
|| extension_loaded('memcache');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a memcached cache provider.
|
||||
*
|
||||
* Has compatibility with both the newer memcached extension and the older memcache extension.
|
||||
* memcached is attempted first.
|
||||
*
|
||||
* @param MemcachedProviderInfo $providerInfo Memcached provider info.
|
||||
* @return MemcachedProvider Memcached provider instance.
|
||||
*/
|
||||
public function createProvider(ICacheProviderInfo $providerInfo): ICacheProvider {
|
||||
if(!($providerInfo instanceof MemcachedProviderInfo))
|
||||
throw new InvalidArgumentException('$providerInfo must by of type MemcachedProviderInfo');
|
||||
|
||||
if(extension_loaded('memcached'))
|
||||
return new MemcachedProviderModern($providerInfo);
|
||||
|
||||
if(extension_loaded('memcache'))
|
||||
return new MemcachedProviderLegacy($providerInfo);
|
||||
|
||||
throw new RuntimeException('Unable to create a memcached provider.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MemcachedProviderInfo Memcached provider info instance.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): ICacheProviderInfo {
|
||||
if(is_string($dsn)) {
|
||||
$dsn = parse_url($dsn);
|
||||
if($dsn === false)
|
||||
throw new InvalidArgumentException('$dsn is not a valid uri.');
|
||||
}
|
||||
|
||||
if(!isset($dsn['host']))
|
||||
throw new InvalidArgumentException('Host is missing from DSN.');
|
||||
|
||||
$host = $dsn['host'];
|
||||
$isPool = $host === ':unix' || $host === ':pool';
|
||||
|
||||
$endPoints = [];
|
||||
if(!$isPool) {
|
||||
if(isset($dsn['port']))
|
||||
$host .= ':' . $dsn['port'];
|
||||
|
||||
$endPoints[] = [EndPoint::parse($host), 0];
|
||||
}
|
||||
|
||||
$endPoint = $isPool ? null : EndPoint::parse($host);
|
||||
$prefixKey = str_replace('/', ':', ltrim($dsn['path'] ?? '', '/'));
|
||||
|
||||
parse_str(str_replace('+', '%2B', $dsn['query'] ?? ''), $query);
|
||||
|
||||
if(isset($query['server'])) {
|
||||
if(is_string($query['server']))
|
||||
$query['server'] = [$query['server']];
|
||||
|
||||
if(is_array($query['server']))
|
||||
foreach($query['server'] as $endPoint) {
|
||||
$parts = explode(';', $endPoint, 2);
|
||||
$weight = count($parts) > 1 ? (int)$parts[1] : 0;
|
||||
$endPoint = EndPoint::parse($parts[0]);
|
||||
$endPoints[] = [$endPoint, $weight];
|
||||
}
|
||||
}
|
||||
|
||||
$persistName = isset($query['persist']) && is_string($query['persist']) ? $query['persist'] : '';
|
||||
$useBinaryProto = empty($query['proto']) || $query['proto'] !== 'ascii';
|
||||
|
||||
if(empty($query['compress']) || !is_string($query['compress']))
|
||||
$enableCompression = true;
|
||||
else {
|
||||
$enableCompression = strtolower($query['compress']);
|
||||
$enableCompression = $enableCompression !== 'no' && $enableCompression !== 'off' && $enableCompression !== 'false' && $enableCompression !== '0';
|
||||
}
|
||||
|
||||
if(empty($query['nodelay']) || !is_string($query['nodelay']))
|
||||
$tcpNoDelay = true;
|
||||
else {
|
||||
$tcpNoDelay = strtolower($query['nodelay']);
|
||||
$tcpNoDelay = $tcpNoDelay !== 'no' && $tcpNoDelay !== 'off' && $tcpNoDelay !== 'false' && $tcpNoDelay !== '0';
|
||||
}
|
||||
|
||||
if(empty($endPoints))
|
||||
throw new InvalidArgumentException('No servers are specified in the DSN.');
|
||||
|
||||
return new MemcachedProviderInfo($endPoints, $prefixKey, $persistName, $useBinaryProto, $enableCompression, $tcpNoDelay);
|
||||
}
|
||||
}
|
27
src/Cache/Memcached/MemcachedProvider.php
Normal file
27
src/Cache/Memcached/MemcachedProvider.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
// MemcachedProvider.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\Memcached;
|
||||
|
||||
use Index\Cache\ICacheProvider;
|
||||
|
||||
/**
|
||||
* Base Memcached provider implementation.
|
||||
*/
|
||||
abstract class MemcachedProvider implements ICacheProvider {
|
||||
public const MAX_TTL = 30 * 24 * 60 * 60;
|
||||
|
||||
public abstract function get(string $key): mixed;
|
||||
public abstract function set(string $key, mixed $value, int $ttl = 0): void;
|
||||
public abstract function delete(string $key): void;
|
||||
public abstract function touch(string $key, int $ttl = 0): void;
|
||||
public abstract function increment(string $key, int $amount = 1): int;
|
||||
public abstract function decrement(string $key, int $amount = 1): int;
|
||||
public abstract function close(): void;
|
||||
|
||||
public function __destruct() {
|
||||
$this->close();
|
||||
}
|
||||
}
|
86
src/Cache/Memcached/MemcachedProviderInfo.php
Normal file
86
src/Cache/Memcached/MemcachedProviderInfo.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
// MemcachedProviderInfo.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\Memcached;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Index\Cache\ICacheProviderInfo;
|
||||
|
||||
/**
|
||||
* Represents Memcached provider info.
|
||||
*/
|
||||
class MemcachedProviderInfo implements ICacheProviderInfo {
|
||||
public function __construct(
|
||||
private array $endPoints,
|
||||
private string $prefixKey,
|
||||
private string $persistName,
|
||||
private bool $useBinaryProto,
|
||||
private bool $enableCompression,
|
||||
private bool $tcpNoDelay,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Gets server endpoints.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEndPoints(): array {
|
||||
return $this->endPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* A prefix that gets applied to every key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrefixKey(): string {
|
||||
return $this->prefixKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the connection should be persistent.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPersistent(): bool {
|
||||
return $this->persistName !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of persistent connection, will return null if disabled.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getPersistentName(): ?string {
|
||||
return $this->persistName === '' ? null : $this->persistName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the binary protocol should be used rather than ASCII.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldUseBinaryProtocol(): bool {
|
||||
return $this->useBinaryProto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether compression should be applied.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldEnableCompression(): bool {
|
||||
return $this->enableCompression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether nagle's algorithm should be disabled on the connection.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldTcpNoDelay(): bool {
|
||||
return $this->tcpNoDelay;
|
||||
}
|
||||
}
|
98
src/Cache/Memcached/MemcachedProviderLegacy.php
Normal file
98
src/Cache/Memcached/MemcachedProviderLegacy.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
// MemcachedProviderLegacy.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\Memcached;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Memcache;
|
||||
use Index\Net\{DnsEndPoint,IPEndPoint,UnixEndPoint};
|
||||
|
||||
/**
|
||||
* Legacy Memcached provider implementation.
|
||||
*/
|
||||
class MemcachedProviderLegacy extends MemcachedProvider {
|
||||
private Memcache $memcache;
|
||||
private string $prefix;
|
||||
private bool $persistent;
|
||||
private bool $compress;
|
||||
|
||||
public function __construct(MemcachedProviderInfo $providerInfo) {
|
||||
$this->prefix = $providerInfo->getPrefixKey();
|
||||
$this->compress = $providerInfo->shouldEnableCompression();
|
||||
$this->persistent = $providerInfo->isPersistent();
|
||||
$this->memcache = new Memcache;
|
||||
|
||||
foreach($providerInfo->getEndPoints() as $endPointInfo) {
|
||||
if($endPointInfo[0] instanceof UnixEndPoint) {
|
||||
$host = 'unix://' . $endPointInfo->getPath();
|
||||
$port = 0;
|
||||
} elseif($endPointInfo[0] instanceof DnsEndPoint) {
|
||||
$host = $endPointInfo->getHost();
|
||||
$port = $endPointInfo->getPort();
|
||||
} elseif($endPointInfo[0] instanceof IPEndPoint) {
|
||||
$host = $endPointInfo->getAddress()->getCleanAddress();
|
||||
$port = $endPointInfo->getPort();
|
||||
} else throw new InvalidArgumentException('One of the servers specified in $providerInfo is not a supported endpoint.');
|
||||
|
||||
$this->memcache->addServer($host, $port, $this->persistent, $endPointInfo[1]);
|
||||
}
|
||||
}
|
||||
|
||||
public function get(string $key): mixed {
|
||||
$value = $this->memcache->get($this->prefix . $key);
|
||||
return $value === false ? null : unserialize($value);
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, int $ttl = 0): void {
|
||||
if($ttl < 0)
|
||||
throw new InvalidArgumentException('$ttl must be equal to or greater than 0.');
|
||||
if($ttl > MemcachedProvider::MAX_TTL)
|
||||
throw new InvalidArgumentException('$ttl may not be greater than 30 days.');
|
||||
if($value === null)
|
||||
return;
|
||||
|
||||
$serialised = serialize($value);
|
||||
$flags = 0;
|
||||
if($this->compress && strlen($serialised) > 100)
|
||||
$flags |= MEMCACHE_COMPRESSED;
|
||||
|
||||
$this->memcache->set($this->prefix . $key, $serialised, $flags, $ttl);
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
$this->memcache->delete($this->prefix . $key);
|
||||
}
|
||||
|
||||
public function touch(string $key, int $ttl = 0): void {
|
||||
$this->set($key, $this->get($key), $ttl);
|
||||
}
|
||||
|
||||
public function increment(string $key, int $amount = 1): int {
|
||||
$key = $this->prefix . $key;
|
||||
$result = $this->memcache->increment($key, $amount);
|
||||
if($result === false) { // @phpstan-ignore-line: PHP documentation states increment returns int|false
|
||||
$result = $amount;
|
||||
$this->memcache->set($key, serialize($result), 0);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function decrement(string $key, int $amount = 1): int {
|
||||
$key = $this->prefix . $key;
|
||||
$result = $this->memcache->decrement($key, $amount);
|
||||
if($result === false) { // @phpstan-ignore-line: PHP documentation states decrement returns int|false
|
||||
$result = $amount * -1;
|
||||
$this->memcache->set($key, serialize($result), 0);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function close(): void {
|
||||
if(!$this->persistent)
|
||||
$this->memcache->close();
|
||||
}
|
||||
}
|
118
src/Cache/Memcached/MemcachedProviderModern.php
Normal file
118
src/Cache/Memcached/MemcachedProviderModern.php
Normal file
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
// MemcachedProviderModern.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\Memcached;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Memcached;
|
||||
use RuntimeException;
|
||||
use Index\Net\{DnsEndPoint,IPEndPoint,UnixEndPoint};
|
||||
|
||||
/**
|
||||
* Base Memcached provider implementation.
|
||||
*/
|
||||
class MemcachedProviderModern extends MemcachedProvider {
|
||||
private Memcached $memcached;
|
||||
|
||||
public function __construct(MemcachedProviderInfo $providerInfo) {
|
||||
$this->memcached = new Memcached($providerInfo->getPersistentName());
|
||||
$this->memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, $providerInfo->shouldUseBinaryProtocol());
|
||||
$this->memcached->setOption(Memcached::OPT_COMPRESSION, $providerInfo->shouldEnableCompression());
|
||||
$this->memcached->setOption(Memcached::OPT_TCP_NODELAY, $providerInfo->shouldTcpNoDelay());
|
||||
$this->memcached->setOption(Memcached::OPT_PREFIX_KEY, $providerInfo->getPrefixKey());
|
||||
|
||||
foreach($providerInfo->getEndPoints() as $endPointInfo) {
|
||||
if($endPointInfo[0] instanceof UnixEndPoint) {
|
||||
$host = $endPointInfo->getPath();
|
||||
$port = 0;
|
||||
} elseif($endPointInfo[0] instanceof DnsEndPoint) {
|
||||
$host = $endPointInfo->getHost();
|
||||
$port = $endPointInfo->getPort();
|
||||
} elseif($endPointInfo[0] instanceof IPEndPoint) {
|
||||
$host = $endPointInfo->getAddress()->getCleanAddress();
|
||||
$port = $endPointInfo->getPort();
|
||||
} else throw new InvalidArgumentException('One of the servers specified in $providerInfo is not a supported endpoint.');
|
||||
|
||||
$this->memcached->addServer($host, $port, $endPointInfo[1]);
|
||||
}
|
||||
}
|
||||
|
||||
public function get(string $key): mixed {
|
||||
$result = $this->memcached->get($key);
|
||||
|
||||
if($result === false) {
|
||||
$result = $this->memcached->getResultCode();
|
||||
if($result === Memcached::RES_NOTFOUND)
|
||||
return null;
|
||||
|
||||
throw new RuntimeException("[Memcached] Error during get: {$result}", $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, int $ttl = 0): void {
|
||||
if($ttl < 0)
|
||||
throw new InvalidArgumentException('$ttl must be equal to or greater than 0.');
|
||||
if($ttl > MemcachedProvider::MAX_TTL)
|
||||
throw new InvalidArgumentException('$ttl may not be greater than 30 days.');
|
||||
|
||||
$result = $this->memcached->set($key, $value, $ttl);
|
||||
|
||||
if(!$result) {
|
||||
$result = $this->memcached->getResultCode();
|
||||
throw new RuntimeException("[Memcached] Error during set: {$result}", $result);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
$result = $this->memcached->delete($key);
|
||||
|
||||
if(!$result) {
|
||||
$result = $this->memcached->getResultCode();
|
||||
if($result !== Memcached::RES_NOTFOUND)
|
||||
throw new RuntimeException("[Memcached] Error during delete: {$result}", $result);
|
||||
}
|
||||
}
|
||||
|
||||
public function touch(string $key, int $ttl = 0): void {
|
||||
if($ttl < 0)
|
||||
throw new InvalidArgumentException('$ttl must be equal to or greater than 0.');
|
||||
if($ttl > MemcachedProvider::MAX_TTL)
|
||||
throw new InvalidArgumentException('$ttl may not be greater than 30 days.');
|
||||
|
||||
$result = $this->memcached->touch($key, $ttl);
|
||||
|
||||
if(!$result) {
|
||||
$result = $this->memcached->getResultCode();
|
||||
throw new RuntimeException("[Memcached] Error during set: {$result}", $result);
|
||||
}
|
||||
}
|
||||
|
||||
public function increment(string $key, int $amount = 1): int {
|
||||
$result = $this->memcached->increment($key, $amount);
|
||||
if($result === false) {
|
||||
$result = $this->memcached->getResultCode();
|
||||
throw new RuntimeException("[Memcached] Error during increment: {$result}", $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function decrement(string $key, int $amount = 1): int {
|
||||
$result = $this->memcached->decrement($key, $amount);
|
||||
if($result === false) {
|
||||
$result = $this->memcached->getResultCode();
|
||||
throw new RuntimeException("[Memcached] Error during decrement: {$result}", $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function close(): void {
|
||||
if(!$this->memcached->isPersistent())
|
||||
$this->memcached->quit();
|
||||
}
|
||||
}
|
86
src/Cache/Valkey/ValkeyBackend.php
Normal file
86
src/Cache/Valkey/ValkeyBackend.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
// ValkeyBackend.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\Valkey;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Cache\{ICacheBackend,ICacheProvider,ICacheProviderInfo};
|
||||
use Index\Net\{EndPoint,UnixEndPoint};
|
||||
|
||||
/**
|
||||
* Information about the Valkey backend.
|
||||
*
|
||||
* Also compatible with Redis and KeyDB.
|
||||
*/
|
||||
class ValkeyBackend implements ICacheBackend {
|
||||
public function isAvailable(): bool {
|
||||
return extension_loaded('redis');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Valkey cache provider.
|
||||
*
|
||||
* @param ValkeyProviderInfo $providerInfo Valkey provider info.
|
||||
* @return ValkeyProvider Valkey provider instance.
|
||||
*/
|
||||
public function createProvider(ICacheProviderInfo $providerInfo): ICacheProvider {
|
||||
if(!($providerInfo instanceof ValkeyProviderInfo))
|
||||
throw new InvalidArgumentException('$providerInfo must by of type ValkeyProviderInfo');
|
||||
|
||||
return new ValkeyProvider($providerInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ValkeyProviderInfo Valkey provider info instance.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): ICacheProviderInfo {
|
||||
if(is_string($dsn)) {
|
||||
$dsn = parse_url($dsn);
|
||||
if($dsn === false)
|
||||
throw new InvalidArgumentException('$dsn is not a valid uri.');
|
||||
}
|
||||
|
||||
if(!isset($dsn['host']))
|
||||
throw new InvalidArgumentException('Host is missing from DSN.');
|
||||
|
||||
$host = $dsn['host'];
|
||||
$isUnix = $host === ':unix';
|
||||
|
||||
if(empty($dsn['user'])) {
|
||||
$username = $password = '';
|
||||
} else {
|
||||
if(isset($dsn['pass'])) {
|
||||
$username = $dsn['user'];
|
||||
$password = $dsn['pass'];
|
||||
} else {
|
||||
$password = $dsn['user'];
|
||||
$username = '';
|
||||
}
|
||||
}
|
||||
|
||||
if(!$isUnix) {
|
||||
if(isset($dsn['port']))
|
||||
$host .= ':' . $dsn['port'];
|
||||
|
||||
$endPoint = EndPoint::parse($host);
|
||||
}
|
||||
|
||||
$prefix = str_replace('/', ':', ltrim($dsn['path'] ?? '', '/'));
|
||||
parse_str(str_replace('+', '%2B', $dsn['query'] ?? ''), $query);
|
||||
|
||||
$unixPath = isset($query['socket']) && is_string($query['socket']) ? $query['socket'] : '';
|
||||
$dbNumber = isset($query['db']) && is_string($query['db']) && ctype_digit($query['db']) ? (int)$query['db'] : 0;
|
||||
$persist = isset($query['persist']);
|
||||
|
||||
if($isUnix) {
|
||||
if(empty($unixPath))
|
||||
throw new InvalidArgumentException('Unix socket path is missing from DSN.');
|
||||
$endPoint = new UnixEndPoint($unixPath);
|
||||
}
|
||||
|
||||
return new ValkeyProviderInfo($endPoint, $prefix, $persist, $username, $password, $dbNumber);
|
||||
}
|
||||
}
|
81
src/Cache/Valkey/ValkeyProvider.php
Normal file
81
src/Cache/Valkey/ValkeyProvider.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
// ValkeyProvider.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\Valkey;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Redis;
|
||||
use RedisException;
|
||||
use Index\Cache\ICacheProvider;
|
||||
use Index\Net\{DnsEndPoint,IPEndPoint,UnixEndPoint};
|
||||
|
||||
/**
|
||||
* Valkey provider implementation.
|
||||
*/
|
||||
class ValkeyProvider implements ICacheProvider {
|
||||
private Redis $redis;
|
||||
private bool $persist;
|
||||
|
||||
public function __construct(ValkeyProviderInfo $providerInfo) {
|
||||
$this->persist = $providerInfo->isPersistent();
|
||||
$this->redis = new Redis;
|
||||
$this->redis->setOption(Redis::OPT_PREFIX, $providerInfo->getPrefix());
|
||||
|
||||
if($this->persist)
|
||||
$this->redis->pconnect($providerInfo->getServerHost(), $providerInfo->getServerPort());
|
||||
else
|
||||
$this->redis->connect($providerInfo->getServerHost(), $providerInfo->getServerPort());
|
||||
|
||||
if($providerInfo->hasPassword()) {
|
||||
if($providerInfo->hasUsername())
|
||||
$this->redis->auth([$providerInfo->getUsername(), $providerInfo->getPassword()]);
|
||||
else
|
||||
$this->redis->auth($providerInfo->getPassword());
|
||||
}
|
||||
|
||||
if($providerInfo->hasDatabaseNumber())
|
||||
$this->redis->select($providerInfo->getDatabaseNumber());
|
||||
}
|
||||
|
||||
public function get(string $key): mixed {
|
||||
$result = $this->redis->get($key);
|
||||
return $result === false ? null : $result;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, int $ttl = 0): void {
|
||||
if($ttl < 0)
|
||||
throw new InvalidArgumentException('$ttl must be equal to or greater than 0.');
|
||||
|
||||
$this->redis->setEx($key, $ttl, $value);
|
||||
}
|
||||
|
||||
public function delete(string $key): void {
|
||||
$this->redis->unlink($key);
|
||||
}
|
||||
|
||||
public function touch(string $key, int $ttl = 0): void {
|
||||
if($ttl < 0)
|
||||
throw new InvalidArgumentException('$ttl must be equal to or greater than 0.');
|
||||
|
||||
$this->redis->expire($key, $ttl);
|
||||
}
|
||||
|
||||
public function increment(string $key, int $amount = 1): int {
|
||||
return $this->redis->incrBy($key, $amount);
|
||||
}
|
||||
|
||||
public function decrement(string $key, int $amount = 1): int {
|
||||
return $this->redis->decrBy($key, $amount);
|
||||
}
|
||||
|
||||
public function close(): void {
|
||||
if(!$this->persist)
|
||||
$this->redis->close();
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->close();
|
||||
}
|
||||
}
|
135
src/Cache/Valkey/ValkeyProviderInfo.php
Normal file
135
src/Cache/Valkey/ValkeyProviderInfo.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
// ValkeyProviderInfo.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Cache\Valkey;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Cache\ICacheProviderInfo;
|
||||
use Index\Net\{DnsEndPoint,EndPoint,IPEndPoint,UnixEndPoint};
|
||||
|
||||
/**
|
||||
* Represents Valkey provider info.
|
||||
*/
|
||||
class ValkeyProviderInfo implements ICacheProviderInfo {
|
||||
public function __construct(
|
||||
private EndPoint $endPoint,
|
||||
private string $prefix,
|
||||
private bool $persist,
|
||||
private string $username,
|
||||
private string $password,
|
||||
private int $dbNumber,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Gets server endpoint.
|
||||
*
|
||||
* @return EndPoint
|
||||
*/
|
||||
public function getEndPoint(): EndPoint {
|
||||
return $this->endPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the server hostname.
|
||||
*
|
||||
* @throws RuntimeException Unsupported endpoint specified.
|
||||
* @return string
|
||||
*/
|
||||
public function getServerHost(): string {
|
||||
if($this->endPoint instanceof UnixEndPoint)
|
||||
return $this->endPoint->getPath();
|
||||
if($this->endPoint instanceof DnsEndPoint)
|
||||
return $this->endPoint->getHost();
|
||||
if($this->endPoint instanceof IPEndPoint)
|
||||
return $this->endPoint->getAddress()->getCleanAddress();
|
||||
|
||||
throw new RuntimeException('EndPoint type is not supported.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the server Unix path.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getServerPort(): int {
|
||||
if($this->endPoint instanceof DnsEndPoint || $this->endPoint instanceof IPEndPoint)
|
||||
return $this->endPoint->getPort();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A prefix that gets applied to every key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrefix(): string {
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the connection should be persistent.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPersistent(): bool {
|
||||
return $this->persist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a username should be used.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUsername(): bool {
|
||||
return $this->username !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Username to authenticate with.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername(): string {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a password should be used.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPassword(): bool {
|
||||
return $this->password !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Password to authenticate with.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPassword(): string {
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a database should be selected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDatabaseNumber(): bool {
|
||||
return $this->dbNumber !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database that should be selected.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDatabaseNumber(): int {
|
||||
return $this->dbNumber;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
<?php
|
||||
// DbTools.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2023-07-21
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
use Countable;
|
||||
use InvalidArgumentException;
|
||||
use Index\Type;
|
||||
|
||||
/**
|
||||
* Common database actions.
|
||||
|
|
189
tests/CacheToolsTest.php
Normal file
189
tests/CacheToolsTest.php
Normal file
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
// CacheToolsTest.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-04-10
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Index\Cache\CacheTools;
|
||||
use Index\Cache\ArrayCache\{ArrayCacheBackend,ArrayCacheProvider};
|
||||
use Index\Cache\Memcached\MemcachedBackend;
|
||||
use Index\Cache\Valkey\ValkeyBackend;
|
||||
use Index\Net\{DnsEndPoint,IPEndPoint,UnixEndPoint};
|
||||
|
||||
/**
|
||||
* @covers CacheTools
|
||||
*/
|
||||
final class CacheToolsTest extends TestCase {
|
||||
public function testBasicDSN(): void {
|
||||
$arrayCache = CacheTools::create('array:');
|
||||
$this->assertInstanceOf(ArrayCacheProvider::class, $arrayCache);
|
||||
|
||||
$arrayCache = CacheTools::create('Index-Cache-ArrayCache-ArrayCacheBackend:');
|
||||
$this->assertInstanceOf(ArrayCacheProvider::class, $arrayCache);
|
||||
}
|
||||
|
||||
public function testMemcachedDSN(): void {
|
||||
$memcached = new MemcachedBackend;
|
||||
|
||||
// unix socket
|
||||
$info = $memcached->parseDsn('memcached://:unix:/prefix/test/?server=/var/run/memcached.sock');
|
||||
|
||||
$endPoints = $info->getEndPoints();
|
||||
$this->assertEquals(1, count($endPoints));
|
||||
$this->assertInstanceOf(UnixEndPoint::class, $endPoints[0][0]);
|
||||
$this->assertEquals('/var/run/memcached.sock', $endPoints[0][0]->getPath());
|
||||
$this->assertEquals(0, $endPoints[0][1]);
|
||||
$this->assertEquals('prefix:test:', $info->getPrefixKey());
|
||||
$this->assertFalse($info->isPersistent());
|
||||
$this->assertNull($info->getPersistentName());
|
||||
$this->assertTrue($info->shouldUseBinaryProtocol());
|
||||
$this->assertTrue($info->shouldEnableCompression());
|
||||
$this->assertTrue($info->shouldTcpNoDelay());
|
||||
|
||||
// ipv4
|
||||
$info = $memcached->parseDsn('memcached://127.0.0.1?compress=no&nodelay=off&persist=test');
|
||||
|
||||
$endPoints = $info->getEndPoints();
|
||||
$this->assertEquals(1, count($endPoints));
|
||||
$this->assertInstanceOf(IPEndPoint::class, $endPoints[0][0]);
|
||||
$this->assertEquals('127.0.0.1', (string)$endPoints[0][0]->getAddress());
|
||||
$this->assertEquals(0, $endPoints[0][0]->getPort());
|
||||
$this->assertEquals(0, $endPoints[0][1]);
|
||||
$this->assertEquals('', $info->getPrefixKey());
|
||||
$this->assertTrue($info->isPersistent());
|
||||
$this->assertEquals('test', $info->getPersistentName());
|
||||
$this->assertTrue($info->shouldUseBinaryProtocol());
|
||||
$this->assertFalse($info->shouldEnableCompression());
|
||||
$this->assertFalse($info->shouldTcpNoDelay());
|
||||
|
||||
// ipv6
|
||||
$info = $memcached->parseDsn('memcached://[::1]:9001?proto=ascii');
|
||||
|
||||
$endPoints = $info->getEndPoints();
|
||||
$this->assertEquals(1, count($endPoints));
|
||||
$this->assertInstanceOf(IPEndPoint::class, $endPoints[0][0]);
|
||||
$this->assertEquals('::1', (string)$endPoints[0][0]->getAddress());
|
||||
$this->assertEquals(9001, $endPoints[0][0]->getPort());
|
||||
$this->assertEquals(0, $endPoints[0][1]);
|
||||
$this->assertEquals('', $info->getPrefixKey());
|
||||
$this->assertFalse($info->isPersistent());
|
||||
$this->assertNull($info->getPersistentName());
|
||||
$this->assertFalse($info->shouldUseBinaryProtocol());
|
||||
$this->assertTrue($info->shouldEnableCompression());
|
||||
$this->assertTrue($info->shouldTcpNoDelay());
|
||||
|
||||
// dns
|
||||
$info = $memcached->parseDsn('memcache://localhost');
|
||||
|
||||
$endPoints = $info->getEndPoints();
|
||||
$this->assertEquals(1, count($endPoints));
|
||||
$this->assertInstanceOf(DnsEndPoint::class, $endPoints[0][0]);
|
||||
$this->assertEquals('localhost', $endPoints[0][0]->getHost());
|
||||
$this->assertEquals(0, $endPoints[0][0]->getPort());
|
||||
$this->assertEquals(0, $endPoints[0][1]);
|
||||
$this->assertEquals('', $info->getPrefixKey());
|
||||
$this->assertFalse($info->isPersistent());
|
||||
$this->assertNull($info->getPersistentName());
|
||||
$this->assertTrue($info->shouldUseBinaryProtocol());
|
||||
$this->assertTrue($info->shouldEnableCompression());
|
||||
$this->assertTrue($info->shouldTcpNoDelay());
|
||||
|
||||
// pool
|
||||
$info = $memcached->parseDsn('memcached://:pool:/?server[]=/var/run/memcached.sock;20&server[]=127.0.0.1;10&server[]=[::1]:9001;5&server[]=localhost');
|
||||
|
||||
$endPoints = $info->getEndPoints();
|
||||
$this->assertEquals(4, count($endPoints));
|
||||
|
||||
$this->assertInstanceOf(UnixEndPoint::class, $endPoints[0][0]);
|
||||
$this->assertEquals('/var/run/memcached.sock', $endPoints[0][0]->getPath());
|
||||
$this->assertEquals(20, $endPoints[0][1]);
|
||||
|
||||
$this->assertInstanceOf(IPEndPoint::class, $endPoints[1][0]);
|
||||
$this->assertEquals('127.0.0.1', (string)$endPoints[1][0]->getAddress());
|
||||
$this->assertEquals(0, $endPoints[1][0]->getPort());
|
||||
$this->assertEquals(10, $endPoints[1][1]);
|
||||
|
||||
$this->assertInstanceOf(IPEndPoint::class, $endPoints[2][0]);
|
||||
$this->assertEquals('::1', (string)$endPoints[2][0]->getAddress());
|
||||
$this->assertEquals(9001, $endPoints[2][0]->getPort());
|
||||
$this->assertEquals(5, $endPoints[2][1]);
|
||||
|
||||
$this->assertInstanceOf(DnsEndPoint::class, $endPoints[3][0]);
|
||||
$this->assertEquals('localhost', $endPoints[3][0]->getHost());
|
||||
$this->assertEquals(0, $endPoints[3][0]->getPort());
|
||||
$this->assertEquals(0, $endPoints[3][1]);
|
||||
|
||||
$this->assertEquals('', $info->getPrefixKey());
|
||||
$this->assertFalse($info->isPersistent());
|
||||
$this->assertNull($info->getPersistentName());
|
||||
$this->assertTrue($info->shouldUseBinaryProtocol());
|
||||
$this->assertTrue($info->shouldEnableCompression());
|
||||
$this->assertTrue($info->shouldTcpNoDelay());
|
||||
}
|
||||
|
||||
public function testValkeyDSN(): void {
|
||||
$valkey = new ValkeyBackend;
|
||||
|
||||
// unix socket
|
||||
$info = $valkey->parseDsn('valkey://:unix:/prefix/test/?socket=/var/run/valkey.sock');
|
||||
|
||||
$this->assertInstanceOf(UnixEndPoint::class, $info->getEndPoint());
|
||||
$this->assertEquals('/var/run/valkey.sock', $info->getServerHost());
|
||||
$this->assertEquals(0, $info->getServerPort());
|
||||
$this->assertEquals('prefix:test:', $info->getPrefix());
|
||||
$this->assertFalse($info->isPersistent());
|
||||
$this->assertFalse($info->hasUsername());
|
||||
$this->assertEmpty($info->getUsername());
|
||||
$this->assertFalse($info->hasPassword());
|
||||
$this->assertEmpty($info->getPassword());
|
||||
$this->assertFalse($info->hasDatabaseNumber());
|
||||
$this->assertEquals(0, $info->getDatabaseNumber());
|
||||
|
||||
// ipv4
|
||||
$info = $valkey->parseDsn('valkey://password@127.0.0.1:6379?db=123');
|
||||
|
||||
$this->assertInstanceOf(IPEndPoint::class, $info->getEndPoint());
|
||||
$this->assertEquals('127.0.0.1', $info->getServerHost());
|
||||
$this->assertEquals(6379, $info->getServerPort());
|
||||
$this->assertEmpty($info->getPrefix());
|
||||
$this->assertFalse($info->isPersistent());
|
||||
$this->assertFalse($info->hasUsername());
|
||||
$this->assertEmpty($info->getUsername());
|
||||
$this->assertTrue($info->hasPassword());
|
||||
$this->assertEquals('password', $info->getPassword());
|
||||
$this->assertTrue($info->hasDatabaseNumber());
|
||||
$this->assertEquals(123, $info->getDatabaseNumber());
|
||||
|
||||
// ipv6
|
||||
$info = $valkey->parseDsn('valkey://username:password@[::1]/?persist&db=1');
|
||||
|
||||
$this->assertInstanceOf(IPEndPoint::class, $info->getEndPoint());
|
||||
$this->assertEquals('::1', $info->getServerHost());
|
||||
$this->assertEquals(0, $info->getServerPort());
|
||||
$this->assertEmpty($info->getPrefix());
|
||||
$this->assertTrue($info->isPersistent());
|
||||
$this->assertTrue($info->hasUsername());
|
||||
$this->assertEquals('username', $info->getUsername());
|
||||
$this->assertTrue($info->hasPassword());
|
||||
$this->assertEquals('password', $info->getPassword());
|
||||
$this->assertTrue($info->hasDatabaseNumber());
|
||||
$this->assertEquals(1, $info->getDatabaseNumber());
|
||||
|
||||
// dns
|
||||
$info = $valkey->parseDsn('valkey://username:@misaka.nl/');
|
||||
|
||||
$this->assertInstanceOf(DnsEndPoint::class, $info->getEndPoint());
|
||||
$this->assertEquals('misaka.nl', $info->getServerHost());
|
||||
$this->assertEquals(0, $info->getServerPort());
|
||||
$this->assertEmpty($info->getPrefix());
|
||||
$this->assertFalse($info->isPersistent());
|
||||
$this->assertTrue($info->hasUsername());
|
||||
$this->assertEquals('username', $info->getUsername());
|
||||
$this->assertFalse($info->hasPassword());
|
||||
$this->assertEquals('', $info->getPassword());
|
||||
$this->assertFalse($info->hasDatabaseNumber());
|
||||
$this->assertEquals(0, $info->getDatabaseNumber());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue