139 lines
5.7 KiB
PHP
139 lines
5.7 KiB
PHP
<?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.');
|
|
}
|
|
|
|
/**
|
|
* Constructs a cache info instance from a dsn.
|
|
*
|
|
* Memcached does not support a username or password.
|
|
*
|
|
* The host part of the URL can be any DNS name, or special values `:unix:` and `:pool:`, documented further down.
|
|
*
|
|
* The path part of the URL is used as a key prefix. Any prefix slashes (`/`) are trimmed and others are converted to a colon (`:`).
|
|
* Meaning `/prefix/test/` is converted to `prefix:test:`.
|
|
*
|
|
* In order to use a Unix socket path, set the host part to `:unix:` and specify `server=/path/to/socket.sock` in the query.
|
|
*
|
|
* In order to use a pool of connections, set the host part to `:pool:` and specify any amount of params `server[]` in the query.
|
|
* Weights can be specified at the end, prefixed by a semicolon (`;`)
|
|
* Examples include:
|
|
* - `server[]=/var/run/memcached.sock;20`
|
|
* - `server[]=127.0.0.1;10`
|
|
* - `server[]=[::1]:9001;5`
|
|
* - `server[]=localhost`
|
|
*
|
|
* Internally `:unix:` and `:pool:` invoke the same behaviour, but please use them appropriately.
|
|
*
|
|
* Other query fields include:
|
|
* - `persist=<name>`: a named persistent connection. Named only applied to the memcached implementation, the legacy memcache implementation treats this as a boolean flag.
|
|
* - `proto=ascii`: Forces the memcached implemenation to use the ASCII protocol over the binary one. The legacy memcache implementation always uses the ASCII protocol.
|
|
* - `compress=<no,off,false,0>`: Turns compression of strings larger than 100 characters off.
|
|
* - `nodelay=<no,off,false,0>`: Turns TCP No Delay off. Has no effect on the legacy memcache implementation.
|
|
*
|
|
* Why is the legacy memcache extension supported? cuz I felt like it.
|
|
*
|
|
* @param string|array $dsn DSN with provider information.
|
|
* @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);
|
|
}
|
|
}
|