Added DSN documentation for Database and Cache backends.

This commit is contained in:
Pachira 2024-04-10 23:26:13 +00:00
parent ad75d72aa0
commit c40f264755
11 changed files with 214 additions and 49 deletions

View file

@ -1 +1 @@
0.2404.102223
0.2404.102325

View file

@ -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 {

View file

@ -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);

View file

@ -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 {

View file

@ -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 {

View file

@ -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);

View file

@ -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))

View file

@ -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 {

View file

@ -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);
}

View file

@ -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());

View file

@ -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