index/src/Cache/Memcached/MemcachedBackend.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);
}
}