Added DSN documentation for Database and Cache backends.
This commit is contained in:
parent
ad75d72aa0
commit
c40f264755
11 changed files with 214 additions and 49 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.2404.102223
|
||||
0.2404.102325
|
||||
|
|
|
@ -30,6 +30,11 @@ class ArrayCacheBackend implements ICacheBackend {
|
|||
}
|
||||
|
||||
/**
|
||||
* Constructs a cache info instance from a dsn.
|
||||
*
|
||||
* ArrayCache has no parameters that can be controlled using the DSN.
|
||||
*
|
||||
* @param string|array $dsn DSN with provider information.
|
||||
* @return ArrayCacheProviderInfo Dummy provider info instance.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): ICacheProviderInfo {
|
||||
|
|
|
@ -10,16 +10,28 @@ use RuntimeException;
|
|||
|
||||
/**
|
||||
* Common cache actions.
|
||||
*
|
||||
* DSN documentation:
|
||||
*
|
||||
* CacheTools only handles the scheme part of the URL,
|
||||
* the rest of the URL is described in the documentation of the parseDsn implementation of the respective backend.
|
||||
*
|
||||
* The scheme can be a PHP classpath to an implementation of ICacheBackend, or any of these aliases:
|
||||
* - `array`, `null`: maps to `ArrayCache\ArrayCacheBackend` or `Index-Cache-ArrayCache-ArrayCacheBackend`, uses a simple array backed cache that doesn't get saved.
|
||||
* - `memcached`, `memcache`: maps to `Memcached\MemcachedBackend` or `Index-Cache-Memcached-MemcachedBackend`, provides a backend based on the memcached or memcache extension.
|
||||
* - `valkey`, `keydb`, `redis`: maps to `Valkey\ValkeyBackend` or `Index-Cache-Valkey-ValkeyBackend`, provides a backend based on the phpredis extension.
|
||||
*
|
||||
* Short names are currently hardcoded and cannot be expanded.
|
||||
*/
|
||||
final class CacheTools {
|
||||
private const CACHE_PROTOS = [
|
||||
'null' => ArrayCache\ArrayCacheBackend::class,
|
||||
'array' => ArrayCache\ArrayCacheBackend::class,
|
||||
'memcache' => Memcached\MemcachedBackend::class,
|
||||
'null' => ArrayCache\ArrayCacheBackend::class,
|
||||
'memcached' => Memcached\MemcachedBackend::class,
|
||||
'memcache' => Memcached\MemcachedBackend::class,
|
||||
'valkey' => Valkey\ValkeyBackend::class,
|
||||
'redis' => Valkey\ValkeyBackend::class,
|
||||
'keydb' => Valkey\ValkeyBackend::class,
|
||||
'valkey' => Valkey\ValkeyBackend::class,
|
||||
];
|
||||
|
||||
private static function parseDsnUri(string $dsn): array {
|
||||
|
@ -60,15 +72,45 @@ final class CacheTools {
|
|||
return $backend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves cache provider based on DSN URL.
|
||||
*
|
||||
* Format of the DSN URLs are described in the documentation of the CacheTools class as a whole.
|
||||
*
|
||||
* @param string $dsn URL to create cache provider from.
|
||||
* @throws InvalidArgumentException if $dsn is not a valid URL.
|
||||
* @throws RuntimeException if no cache provider can be made using the URL.
|
||||
* @return ICacheBackend Cache backend instance.
|
||||
*/
|
||||
public static function backend(string $dsn): ICacheBackend {
|
||||
return self::resolveBackend(self::parseDsnUri($dsn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a DSN URL.
|
||||
*
|
||||
* Format of the DSN URLs are described in the documentation of the CacheTools class as a whole.
|
||||
*
|
||||
* @param string $dsn URL to create cache provider from.
|
||||
* @throws InvalidArgumentException if $dsn is not a valid URL.
|
||||
* @throws RuntimeException if no cache provider can be made using the URL.
|
||||
* @return ICacheProviderInfo Cache provider info.
|
||||
*/
|
||||
public static function parse(string $dsn): ICacheProviderInfo {
|
||||
$uri = self::parseDsnUri($dsn);
|
||||
return self::resolveBackend($uri)->parseDsn($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a DSN URL to create a cache provider instance.
|
||||
*
|
||||
* Format of the DSN URLs are described in the documentation of the CacheTools class as a whole.
|
||||
*
|
||||
* @param string $dsn URL to create cache provider from.
|
||||
* @throws InvalidArgumentException if $dsn is not a valid URL.
|
||||
* @throws RuntimeException if no cache provider can be made using the URL.
|
||||
* @return ICacheProvider A cache provider.
|
||||
*/
|
||||
public static function create(string $dsn): ICacheProvider {
|
||||
$uri = self::parseDsnUri($dsn);
|
||||
$backend = self::resolveBackend($uri);
|
||||
|
|
|
@ -42,6 +42,36 @@ class MemcachedBackend implements ICacheBackend {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
|
|
@ -34,6 +34,25 @@ class ValkeyBackend implements ICacheBackend {
|
|||
}
|
||||
|
||||
/**
|
||||
* Constructs a cache info instance from a dsn.
|
||||
*
|
||||
* The Valkey backend supports setting a username and password in the URL.
|
||||
* `//username:password@` is treated as a username and password.
|
||||
* `//username@` is treated as just a password despite what is normally expected of a URL. Older versions of Redis did not have the concept of usernames.
|
||||
* In order to still use a username but no password for some reason you can specify `//username:@`, just inserting a colon without anything after it.
|
||||
*
|
||||
* The host part of the URL can be any DNS name, or special value `:unix:`, 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 `socket=/path/to/socket.sock` in the query.
|
||||
*
|
||||
* Other query fields include:
|
||||
* - `db=<number>`: Allows you to select a different database.
|
||||
* - `persist`: Uses a persistent connection.
|
||||
*
|
||||
* @param string|array $dsn DSN with provider information.
|
||||
* @return ValkeyProviderInfo Valkey provider info instance.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): ICacheProviderInfo {
|
||||
|
|
|
@ -10,6 +10,18 @@ use InvalidArgumentException;
|
|||
|
||||
/**
|
||||
* Common database actions.
|
||||
*
|
||||
* DSN documentation:
|
||||
*
|
||||
* DbTools only handles the scheme part of the URL,
|
||||
* the rest of the URL is described in the documentation of the parseDsn implementation of the respective backend.
|
||||
*
|
||||
* The scheme can be a PHP classpath to an implementation of IDbBackend, or any of these aliases:
|
||||
* - `null`: maps to `NullDb\NullDbBackend` or `Index-Data-NullDb-NullDbBackend`, provides a fallback blackhole database backend.
|
||||
* - `mariadb`, `mysql`: maps to `MariaDB\MariaDBBackend` or `Index-Data-MariaDB-MariaDBBackend`, provides a backend based on the mysqli extension.
|
||||
* - `sqlite`, `sqlite3`: maps to `SQLite\SQLiteBackend` or `Index-Data-SQLite-SQLiteBackend`, provides a backend based on the sqlite3 extension.
|
||||
*
|
||||
* Short names are currently hardcoded and cannot be expanded.
|
||||
*/
|
||||
final class DbTools {
|
||||
private const DB_PROTOS = [
|
||||
|
@ -58,15 +70,45 @@ final class DbTools {
|
|||
return $backend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves database backend based on DSN URL.
|
||||
*
|
||||
* Format of the DSN URLs are described in the documentation of the DbTools class as a whole.
|
||||
*
|
||||
* @param string $dsn URL to create database connection from.
|
||||
* @throws InvalidArgumentException if $dsn is not a valid URL.
|
||||
* @throws DataException if no database connection can be made using the URL.
|
||||
* @return IDbBackend Database backend instance.
|
||||
*/
|
||||
public static function backend(string $dsn): IDbBackend {
|
||||
return self::resolveBackend(self::parseDsnUri($dsn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a DSN URL.
|
||||
*
|
||||
* Format of the DSN URLs are described in the documentation of the DbTools class as a whole.
|
||||
*
|
||||
* @param string $dsn URL to create database connection from.
|
||||
* @throws InvalidArgumentException if $dsn is not a valid URL.
|
||||
* @throws DataException if no database connection can be made using the URL.
|
||||
* @return IDbConnectionInfo Database connection info.
|
||||
*/
|
||||
public static function parse(string $dsn): IDbConnectionInfo {
|
||||
$uri = self::parseDsnUri($dsn);
|
||||
return self::resolveBackend($uri)->parseDsn($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a DSN URL to create a database instance.
|
||||
*
|
||||
* Format of the DSN URLs are described in the documentation of the DbTools class as a whole.
|
||||
*
|
||||
* @param string $dsn URL to create database connection from.
|
||||
* @throws InvalidArgumentException if $dsn is not a valid URL.
|
||||
* @throws DataException if no database connection can be made using the URL.
|
||||
* @return IDbConnection An active database connection.
|
||||
*/
|
||||
public static function create(string $dsn): IDbConnection {
|
||||
$uri = self::parseDsnUri($dsn);
|
||||
$backend = self::resolveBackend($uri);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
// MariaDBBackend.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2023-01-01
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
|
@ -56,6 +56,32 @@ class MariaDBBackend implements IDbBackend {
|
|||
}
|
||||
|
||||
/**
|
||||
* Constructs a connection info instance from a dsn.
|
||||
*
|
||||
* A username and password can be specified in their respective parts of the URL.
|
||||
*
|
||||
* The host part of the URL can be any DNS name, or special value `:unix:`, documented further down.
|
||||
*
|
||||
* The path part of the URL is used as the database name. Any prefix or suffix slashes (`/`) are trimmed and others are converted to an underscore (`_`).
|
||||
* Meaning `/slash/test/` is converted to `slash_test`.
|
||||
*
|
||||
* In order to use a Unix socket path, set the host part to `:unix:` and specify `socket=/path/to/socket.sock` in the query.
|
||||
*
|
||||
* Other supported query parameters are:
|
||||
* - `charset=<name>`: Specifies the character set to use for the connection.
|
||||
* - `init=<query>`: Any arbitrary SQL command to execute open connecting.
|
||||
* - `enc_key=<path>`: Path to a private key file for SSL.
|
||||
* - `enc_cert=<path>`: Path to a certificate file for SSL.
|
||||
* - `enc_authority=<path>`: Path to a certificate authority file for SSL.
|
||||
* - `enc_trusted_certs=<path>`: Path to a directory that contains PEM formatted SSL CA certificates.
|
||||
* - `enc_ciphers=<list>`: A list of allowable ciphers to use for SSL encryption.
|
||||
* - `enc_no_verify`: Disables verification of the server certificate, allows for MITM attacks.
|
||||
* - `compress`: Enables protocol compression.
|
||||
*
|
||||
* Previously supported query parameters:
|
||||
* - `enc_verify=<anything>`: Enabled verification of server certificate. Replaced with `enc_no_verify` as it now defaults to on.
|
||||
*
|
||||
* @param string|array $dsn DSN with connection information.
|
||||
* @return MariaDBConnectionInfo MariaDB connection info.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): IDbConnectionInfo {
|
||||
|
@ -81,30 +107,18 @@ class MariaDBBackend implements IDbBackend {
|
|||
$endPoint = $needsUnix ? null : EndPoint::parse($host);
|
||||
$dbName = str_replace('/', '_', trim($dsn['path'], '/')); // cute for table prefixes i think
|
||||
|
||||
if(!isset($dsn['query'])) {
|
||||
$charSet = null;
|
||||
$initCommand = null;
|
||||
$keyPath = null;
|
||||
$certPath = null;
|
||||
$certAuthPath = null;
|
||||
$trustedCertsPath = null;
|
||||
$cipherAlgos = null;
|
||||
$verifyCert = false;
|
||||
$useCompression = false;
|
||||
} else {
|
||||
parse_str(str_replace('+', '%2B', $dsn['query']), $query);
|
||||
parse_str(str_replace('+', '%2B', $dsn['query'] ?? ''), $query);
|
||||
|
||||
$unixPath = $query['socket'] ?? null;
|
||||
$charSet = $query['charset'] ?? null;
|
||||
$initCommand = $query['init'] ?? null;
|
||||
$keyPath = $query['enc_key'] ?? null;
|
||||
$certPath = $query['enc_cert'] ?? null;
|
||||
$certAuthPath = $query['enc_authority'] ?? null;
|
||||
$trustedCertsPath = $query['enc_trusted_certs'] ?? null;
|
||||
$cipherAlgos = $query['enc_ciphers'] ?? null;
|
||||
$verifyCert = !empty($query['enc_verify']);
|
||||
$useCompression = !empty($query['compress']);
|
||||
}
|
||||
$unixPath = $query['socket'] ?? null;
|
||||
$charSet = $query['charset'] ?? null;
|
||||
$initCommand = $query['init'] ?? null;
|
||||
$keyPath = $query['enc_key'] ?? null;
|
||||
$certPath = $query['enc_cert'] ?? null;
|
||||
$certAuthPath = $query['enc_authority'] ?? null;
|
||||
$trustedCertsPath = $query['enc_trusted_certs'] ?? null;
|
||||
$cipherAlgos = $query['enc_ciphers'] ?? null;
|
||||
$verifyCert = !isset($query['enc_no_verify']);
|
||||
$useCompression = isset($query['compress']);
|
||||
|
||||
if($needsUnix) {
|
||||
if(empty($unixPath))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
// NullDbBackend.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-28
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Data\NullDb;
|
||||
|
||||
|
@ -28,6 +28,11 @@ class NullDbBackend implements IDbBackend {
|
|||
}
|
||||
|
||||
/**
|
||||
* Constructs a connection info instance from a dsn.
|
||||
*
|
||||
* NullDb has no parameters that can be controlled using the DSN.
|
||||
*
|
||||
* @param string|array $dsn DSN with connection information.
|
||||
* @return NullDbConnectionInfo Dummy connection info instance.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): IDbConnectionInfo {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
// SQLiteBackend.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-28
|
||||
// Updated: 2024-04-10
|
||||
|
||||
namespace Index\Data\SQLite;
|
||||
|
||||
|
@ -43,6 +43,18 @@ class SQLiteBackend implements IDbBackend {
|
|||
}
|
||||
|
||||
/**
|
||||
* Constructs a connection info instance from a dsn.
|
||||
*
|
||||
* SQLite DSNs have no username, password, host or port parts.
|
||||
*
|
||||
* The path can be set to any location on the filesystem to specify what file to use, or the special `:memory:` value to use an in-memory database.
|
||||
*
|
||||
* Supported query parameters are:
|
||||
* - `key=<string>` to specify an encryption key.
|
||||
* - `readOnly` to open a database in read-only mode.
|
||||
* - `openOnly` to prevent a new file from being created if the specified path does not exist.
|
||||
*
|
||||
* @param string|array $dsn DSN with connection information.
|
||||
* @return SQLiteConnectionInfo SQLite connection info.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): IDbConnectionInfo {
|
||||
|
@ -52,22 +64,18 @@ class SQLiteBackend implements IDbBackend {
|
|||
throw new InvalidArgumentException('$dsn is not a valid uri.');
|
||||
}
|
||||
|
||||
$encKey = '';
|
||||
|
||||
$path = $dsn['path'] ?? '';
|
||||
|
||||
if($path === 'memory:')
|
||||
$path = ':memory:';
|
||||
|
||||
if(!isset($dsn['query'])) {
|
||||
$encKey = '';
|
||||
$readOnly = false;
|
||||
$create = true;
|
||||
} else {
|
||||
parse_str(str_replace('+', '%2B', $dsn['query']), $query);
|
||||
parse_str(str_replace('+', '%2B', $dsn['query'] ?? ''), $query);
|
||||
|
||||
$encKey = $query['key'] ?? '';
|
||||
$readOnly = !empty($query['readOnly']);
|
||||
$create = empty($query['openOnly']);
|
||||
}
|
||||
$encKey = $query['key'] ?? '';
|
||||
$readOnly = isset($query['readOnly']);
|
||||
$create = !isset($query['openOnly']);
|
||||
|
||||
return new SQLiteConnectionInfo($path, $encKey, $readOnly, $create);
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ final class CacheToolsTest extends TestCase {
|
|||
$this->assertEquals(0, $info->getDatabaseNumber());
|
||||
|
||||
// ipv4
|
||||
$info = $valkey->parseDsn('valkey://password@127.0.0.1:6379?db=123');
|
||||
$info = $valkey->parseDsn('keydb://password@127.0.0.1:6379?db=123');
|
||||
|
||||
$this->assertInstanceOf(IPEndPoint::class, $info->getEndPoint());
|
||||
$this->assertEquals('127.0.0.1', $info->getServerHost());
|
||||
|
@ -157,7 +157,7 @@ final class CacheToolsTest extends TestCase {
|
|||
$this->assertEquals(123, $info->getDatabaseNumber());
|
||||
|
||||
// ipv6
|
||||
$info = $valkey->parseDsn('valkey://username:password@[::1]/?persist&db=1');
|
||||
$info = $valkey->parseDsn('redis://username:password@[::1]/?persist&db=1');
|
||||
|
||||
$this->assertInstanceOf(IPEndPoint::class, $info->getEndPoint());
|
||||
$this->assertEquals('::1', $info->getServerHost());
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
// DbToolsTest.php
|
||||
// Created: 2021-04-28
|
||||
// Updated: 2022-02-28
|
||||
// Updated: 2024-04-10
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
@ -57,7 +57,7 @@ final class DbToolsTest extends TestCase {
|
|||
$this->assertNull($mci1->getCertificateAuthorityPath());
|
||||
$this->assertNull($mci1->getTrustedCertificatesPath());
|
||||
$this->assertNull($mci1->getCipherAlgorithms());
|
||||
$this->assertFalse($mci1->shouldVerifyCertificate());
|
||||
$this->assertTrue($mci1->shouldVerifyCertificate());
|
||||
$this->assertFalse($mci1->shouldUseCompression());
|
||||
|
||||
// generic ipv4
|
||||
|
@ -79,7 +79,7 @@ final class DbToolsTest extends TestCase {
|
|||
$this->assertNull($mci2->getCertificateAuthorityPath());
|
||||
$this->assertNull($mci2->getTrustedCertificatesPath());
|
||||
$this->assertNull($mci2->getCipherAlgorithms());
|
||||
$this->assertFalse($mci2->shouldVerifyCertificate());
|
||||
$this->assertTrue($mci2->shouldVerifyCertificate());
|
||||
$this->assertFalse($mci2->shouldUseCompression());
|
||||
|
||||
// generic ipv6 with port
|
||||
|
@ -101,21 +101,21 @@ final class DbToolsTest extends TestCase {
|
|||
$this->assertNull($mci3->getCertificateAuthorityPath());
|
||||
$this->assertNull($mci3->getTrustedCertificatesPath());
|
||||
$this->assertNull($mci3->getCipherAlgorithms());
|
||||
$this->assertFalse($mci3->shouldVerifyCertificate());
|
||||
$this->assertTrue($mci3->shouldVerifyCertificate());
|
||||
$this->assertFalse($mci3->shouldUseCompression());
|
||||
|
||||
// sqlite normal
|
||||
$sql1 = $sqlite->parseDsn('sqlite:/path/to/database.db?key=ebwOKzGkLYIxDGXk&readOnly=0&openOnly=1');
|
||||
$sql1 = $sqlite->parseDsn('sqlite:/path/to/database.db?key=ebwOKzGkLYIxDGXk&openOnly');
|
||||
$this->assertEquals('/path/to/database.db', $sql1->getFileName());
|
||||
$this->assertEquals('ebwOKzGkLYIxDGXk', $sql1->getEncryptionKey());
|
||||
$this->assertFalse($sql1->shouldReadOnly());
|
||||
$this->assertFalse($sql1->shouldCreate());
|
||||
|
||||
// sqlite temp file
|
||||
$sql1 = $sqlite->parseDsn('sqlite:?key=Y63vrCttK8bEdPUIXVCOiLQKIgdbUn8V');
|
||||
$sql1 = $sqlite->parseDsn('sqlite:?key=Y63vrCttK8bEdPUIXVCOiLQKIgdbUn8V&readOnly');
|
||||
$this->assertEquals('', $sql1->getFileName());
|
||||
$this->assertEquals('Y63vrCttK8bEdPUIXVCOiLQKIgdbUn8V', $sql1->getEncryptionKey());
|
||||
$this->assertFalse($sql1->shouldReadOnly());
|
||||
$this->assertTrue($sql1->shouldReadOnly());
|
||||
$this->assertTrue($sql1->shouldCreate());
|
||||
|
||||
// sqlite memory
|
||||
|
|
Loading…
Reference in a new issue