Compare commits
2 commits
b4ff61ad96
...
bf7977f511
Author | SHA1 | Date | |
---|---|---|---|
bf7977f511 | |||
0ebd0fa298 |
17 changed files with 428 additions and 335 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.2410.160124
|
||||
0.2410.182054
|
||||
|
|
|
@ -32,6 +32,14 @@
|
|||
"autoload": {
|
||||
"psr-4": {
|
||||
"Index\\": "src"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src/Cache/ArrayCache/_ndx.php",
|
||||
"src/Cache/Memcached/_ndx.php",
|
||||
"src/Cache/Valkey/_ndx.php",
|
||||
"src/Db/MariaDb/_ndx.php",
|
||||
"src/Db/NullDb/_ndx.php",
|
||||
"src/Db/Sqlite/_ndx.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
22
composer.lock
generated
22
composer.lock
generated
|
@ -943,16 +943,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.12.6",
|
||||
"version": "1.12.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae"
|
||||
"reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae",
|
||||
"reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc2b9976bd8b0f84ec9b0e50cc35378551de7af0",
|
||||
"reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -997,7 +997,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-06T15:03:59+00:00"
|
||||
"time": "2024-10-18T11:12:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
|
@ -1594,16 +1594,16 @@
|
|||
},
|
||||
{
|
||||
"name": "sebastian/comparator",
|
||||
"version": "6.1.0",
|
||||
"version": "6.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/comparator.git",
|
||||
"reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d"
|
||||
"reference": "5ef523a49ae7a302b87b2102b72b1eda8918d686"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa37b9e2ca618cb051d71b60120952ee8ca8b03d",
|
||||
"reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5ef523a49ae7a302b87b2102b72b1eda8918d686",
|
||||
"reference": "5ef523a49ae7a302b87b2102b72b1eda8918d686",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1659,7 +1659,7 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/comparator/issues",
|
||||
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/comparator/tree/6.1.0"
|
||||
"source": "https://github.com/sebastianbergmann/comparator/tree/6.1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1667,7 +1667,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-11T15:42:56+00:00"
|
||||
"time": "2024-10-18T15:00:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/complexity",
|
||||
|
|
11
src/Cache/ArrayCache/_ndx.php
Normal file
11
src/Cache/ArrayCache/_ndx.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// _ndx.php
|
||||
// Created: 2024-10-18
|
||||
// Updated: 2024-10-18
|
||||
|
||||
namespace Index\Cache\ArrayCache;
|
||||
|
||||
use Index\Cache\CacheBackends;
|
||||
|
||||
CacheBackends::register('array', ArrayCacheBackend::class);
|
||||
CacheBackends::register('null', ArrayCacheBackend::class);
|
108
src/Cache/CacheBackends.php
Normal file
108
src/Cache/CacheBackends.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
// CacheBackends.php
|
||||
// Created: 2024-10-18
|
||||
// Updated: 2024-10-18
|
||||
|
||||
namespace Index\Cache;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Provides a registry of cache backends.
|
||||
*/
|
||||
final class CacheBackends {
|
||||
/** @var array<string, string> */
|
||||
private static array $schemes = [];
|
||||
|
||||
/** @var array<string, CacheBackend> */
|
||||
private static array $backends = [];
|
||||
|
||||
/**
|
||||
* @throws RuntimeException Will always throw because this is a static class exclusively.
|
||||
*/
|
||||
public function __construct() {
|
||||
throw new RuntimeException('This is a static class, you cannot create an instance of it.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a cache backend with a protocol scheme.
|
||||
*
|
||||
* @param string $scheme URL scheme.
|
||||
* @param string $className Fully qualified class name of the backend.
|
||||
* @throws InvalidArgumentException If $scheme is already registered.
|
||||
*/
|
||||
public static function register(string $scheme, string $className): void {
|
||||
$scheme = strtolower($scheme);
|
||||
if(array_key_exists($scheme, self::$schemes))
|
||||
throw new InvalidArgumentException('$scheme has already been registered');
|
||||
|
||||
self::$schemes[$scheme] = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cache provider from a DSN URI.
|
||||
*
|
||||
* @param string $uri DSN URI.
|
||||
* @throws RuntimeException If the requested cache backend is not available.
|
||||
* @return CacheProvider Connection instance.
|
||||
*/
|
||||
public static function create(string $uri): CacheProvider {
|
||||
$parsed = self::parseUri($uri);
|
||||
|
||||
$backend = self::backend($parsed['scheme']);
|
||||
if(!$backend->isAvailable())
|
||||
throw new RuntimeException('requested cache backend is not available');
|
||||
|
||||
return $backend->createProvider($backend->parseDsn($parsed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a DSN URI into a provider info class.
|
||||
*
|
||||
* @param string $uri DSN URI.
|
||||
* @return CacheProviderInfo Provider info instance.
|
||||
*/
|
||||
public static function parse(string $uri): CacheProviderInfo {
|
||||
$parsed = self::parseUri($uri);
|
||||
return self::backend($parsed['scheme'])->parseDsn($parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cache provider information class from a scheme.
|
||||
*
|
||||
* @param string $scheme Cache protocol scheme.
|
||||
* @throws InvalidArgumentException If $scheme is not a registered protocol scheme.
|
||||
* @throws RuntimeException If the implementation class is not available or doesn't implement CacheBackend.
|
||||
* @return CacheBackend Cache backend information instance.
|
||||
*/
|
||||
public static function backend(string $scheme): CacheBackend {
|
||||
$scheme = strtolower($scheme);
|
||||
if(!array_key_exists($scheme, self::$schemes))
|
||||
throw new InvalidArgumentException('$scheme is not a registered scheme');
|
||||
|
||||
$className = self::$schemes[$scheme];
|
||||
if(array_key_exists($className, self::$backends))
|
||||
return self::$backends[$className];
|
||||
|
||||
if(!class_exists($className))
|
||||
throw new RuntimeException('implementation class for the given $scheme does not exist');
|
||||
|
||||
$backend = new $className;
|
||||
if(!($backend instanceof CacheBackend))
|
||||
throw new RuntimeException('implementation class for the given $scheme does not implement CacheBackend');
|
||||
|
||||
self::$backends[$className] = $backend;
|
||||
return $backend;
|
||||
}
|
||||
|
||||
/** @return array{scheme: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string} */
|
||||
private static function parseUri(string $uri): array {
|
||||
$uri = parse_url($uri);
|
||||
if($uri === false)
|
||||
throw new InvalidArgumentException('$uri is not a valid uri');
|
||||
if(empty($uri['scheme']))
|
||||
throw new InvalidArgumentException('$uri must contain a non-empty scheme component');
|
||||
return $uri;
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
<?php
|
||||
// CacheTools.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-10-16
|
||||
|
||||
namespace Index\Cache;
|
||||
|
||||
use InvalidArgumentException;
|
||||
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 CacheBackend, or any of these aliases:
|
||||
* - `array`, `null`: maps to `ArrayCache\ArrayCacheBackend`, uses a simple array backed cache that doesn't get saved.
|
||||
* - `memcached`, `memcache`: maps to `Memcached\MemcachedBackend`, provides a backend based on the memcached or memcache extension.
|
||||
* - `valkey`, `keydb`, `redis`: maps to `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 = [
|
||||
'array' => ArrayCache\ArrayCacheBackend::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,
|
||||
];
|
||||
|
||||
/** @return array<string, int|string> */
|
||||
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;
|
||||
}
|
||||
|
||||
/** @param array<string, int|string> $uri */
|
||||
private static function resolveBackend(array $uri): CacheBackend {
|
||||
static $backends = null;
|
||||
if(!is_array($backends))
|
||||
$backends = [];
|
||||
|
||||
$scheme = $uri['scheme'];
|
||||
$backend = $backends[$scheme] ?? null;
|
||||
|
||||
if(!($backend instanceof CacheBackend)) {
|
||||
if(!array_key_exists($scheme, self::CACHE_PROTOS))
|
||||
throw new RuntimeException('No implementation is available for the specified scheme.');
|
||||
|
||||
$backend = new (self::CACHE_PROTOS[$scheme]);
|
||||
if(!$backend->isAvailable())
|
||||
throw new RuntimeException('Requested cache backend is not available, likely due to missing dependencies.');
|
||||
|
||||
$backends[$scheme] = $backend;
|
||||
}
|
||||
|
||||
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 CacheBackend Cache backend instance.
|
||||
*/
|
||||
public static function backend(string $dsn): CacheBackend {
|
||||
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 CacheProviderInfo Cache provider info.
|
||||
*/
|
||||
public static function parse(string $dsn): CacheProviderInfo {
|
||||
$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 CacheProvider A cache provider.
|
||||
*/
|
||||
public static function create(string $dsn): CacheProvider {
|
||||
$uri = self::parseDsnUri($dsn);
|
||||
$backend = self::resolveBackend($uri);
|
||||
return $backend->createProvider($backend->parseDsn($uri));
|
||||
}
|
||||
}
|
11
src/Cache/Memcached/_ndx.php
Normal file
11
src/Cache/Memcached/_ndx.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// _ndx.php
|
||||
// Created: 2024-10-18
|
||||
// Updated: 2024-10-18
|
||||
|
||||
namespace Index\Cache\Memcached;
|
||||
|
||||
use Index\Cache\CacheBackends;
|
||||
|
||||
CacheBackends::register('memcached', MemcachedBackend::class);
|
||||
CacheBackends::register('memcache', MemcachedBackend::class);
|
12
src/Cache/Valkey/_ndx.php
Normal file
12
src/Cache/Valkey/_ndx.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
// _ndx.php
|
||||
// Created: 2024-10-18
|
||||
// Updated: 2024-10-18
|
||||
|
||||
namespace Index\Cache\Valkey;
|
||||
|
||||
use Index\Cache\CacheBackends;
|
||||
|
||||
CacheBackends::register('valkey', ValkeyBackend::class);
|
||||
CacheBackends::register('redis', ValkeyBackend::class);
|
||||
CacheBackends::register('keydb', ValkeyBackend::class);
|
108
src/Db/DbBackends.php
Normal file
108
src/Db/DbBackends.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
// DbBackends.php
|
||||
// Created: 2024-10-18
|
||||
// Updated: 2024-10-18
|
||||
|
||||
namespace Index\Db;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Provides a registry of database backends.
|
||||
*/
|
||||
final class DbBackends {
|
||||
/** @var array<string, string> */
|
||||
private static array $schemes = [];
|
||||
|
||||
/** @var array<string, DbBackend> */
|
||||
private static array $backends = [];
|
||||
|
||||
/**
|
||||
* @throws RuntimeException Will always throw because this is a static class exclusively.
|
||||
*/
|
||||
public function __construct() {
|
||||
throw new RuntimeException('This is a static class, you cannot create an instance of it.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a database backend with a protocol scheme.
|
||||
*
|
||||
* @param string $scheme URL scheme.
|
||||
* @param string $className Fully qualified class name of the backend.
|
||||
* @throws InvalidArgumentException If $scheme is already registered.
|
||||
*/
|
||||
public static function register(string $scheme, string $className): void {
|
||||
$scheme = strtolower($scheme);
|
||||
if(array_key_exists($scheme, self::$schemes))
|
||||
throw new InvalidArgumentException('$scheme has already been registered');
|
||||
|
||||
self::$schemes[$scheme] = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a database connection from a DSN URI.
|
||||
*
|
||||
* @param string $uri DSN URI.
|
||||
* @throws RuntimeException If the requested database backend is not available.
|
||||
* @return DbConnection Connection instance.
|
||||
*/
|
||||
public static function create(string $uri): DbConnection {
|
||||
$parsed = self::parseUri($uri);
|
||||
|
||||
$backend = self::backend($parsed['scheme']);
|
||||
if(!$backend->isAvailable())
|
||||
throw new RuntimeException('requested database backend is not available');
|
||||
|
||||
return $backend->createConnection($backend->parseDsn($parsed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a DSN URI into a connection info class.
|
||||
*
|
||||
* @param string $uri DSN URI.
|
||||
* @return DbConnectionInfo Connection info instance.
|
||||
*/
|
||||
public static function parse(string $uri): DbConnectionInfo {
|
||||
$parsed = self::parseUri($uri);
|
||||
return self::backend($parsed['scheme'])->parseDsn($parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a database backend information class from a scheme.
|
||||
*
|
||||
* @param string $scheme Database protocol scheme.
|
||||
* @throws InvalidArgumentException If $scheme is not a registered protocol scheme.
|
||||
* @throws RuntimeException If the implementation class is not available or doesn't implement DbBackend.
|
||||
* @return DbBackend Database backend information instance.
|
||||
*/
|
||||
public static function backend(string $scheme): DbBackend {
|
||||
$scheme = strtolower($scheme);
|
||||
if(!array_key_exists($scheme, self::$schemes))
|
||||
throw new InvalidArgumentException('$scheme is not a registered scheme');
|
||||
|
||||
$className = self::$schemes[$scheme];
|
||||
if(array_key_exists($className, self::$backends))
|
||||
return self::$backends[$className];
|
||||
|
||||
if(!class_exists($className))
|
||||
throw new RuntimeException('implementation class for the given $scheme does not exist');
|
||||
|
||||
$backend = new $className;
|
||||
if(!($backend instanceof DbBackend))
|
||||
throw new RuntimeException('implementation class for the given $scheme does not implement DbBackend');
|
||||
|
||||
self::$backends[$className] = $backend;
|
||||
return $backend;
|
||||
}
|
||||
|
||||
/** @return array{scheme: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string} */
|
||||
private static function parseUri(string $uri): array {
|
||||
$uri = parse_url($uri);
|
||||
if($uri === false)
|
||||
throw new InvalidArgumentException('$uri is not a valid uri');
|
||||
if(empty($uri['scheme']))
|
||||
throw new InvalidArgumentException('$uri must contain a non-empty scheme component');
|
||||
return $uri;
|
||||
}
|
||||
}
|
|
@ -6,109 +6,11 @@
|
|||
namespace Index\Db;
|
||||
|
||||
use Countable;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* 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 DbBackend, or any of these aliases:
|
||||
* - `null`: maps to `NullDb\NullDbBackend`, provides a fallback blackhole database backend.
|
||||
* - `mariadb`, `mysql`: maps to `MariaDb\MariaDbBackend`, provides a backend based on the mysqli extension.
|
||||
* - `sqlite`, `sqlite3`: maps to `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 = [
|
||||
'null' => NullDb\NullDbBackend::class,
|
||||
'mariadb' => MariaDb\MariaDbBackend::class,
|
||||
'mysql' => MariaDb\MariaDbBackend::class,
|
||||
'sqlite' => Sqlite\SqliteBackend::class,
|
||||
'sqlite3' => Sqlite\SqliteBackend::class,
|
||||
];
|
||||
|
||||
/** @return array<string, string|int> */
|
||||
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;
|
||||
}
|
||||
|
||||
/** @param array<string, string|int> $uri */
|
||||
private static function resolveBackend(array $uri): DbBackend {
|
||||
static $backends = null;
|
||||
if(!is_array($backends))
|
||||
$backends = [];
|
||||
|
||||
$scheme = $uri['scheme'];
|
||||
$backend = $backends[$scheme] ?? null;
|
||||
|
||||
if(!($backend instanceof DbBackend)) {
|
||||
if(!array_key_exists($scheme, self::DB_PROTOS))
|
||||
throw new RuntimeException('No implementation is available for the specified scheme.');
|
||||
|
||||
$backend = new (self::DB_PROTOS[$scheme]);
|
||||
if(!$backend->isAvailable())
|
||||
throw new RuntimeException('Requested database backend is not available, likely due to missing dependencies.');
|
||||
|
||||
$backends[$scheme] = $backend;
|
||||
}
|
||||
|
||||
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 RuntimeException if no database connection can be made using the URL.
|
||||
* @return DbBackend Database backend instance.
|
||||
*/
|
||||
public static function backend(string $dsn): DbBackend {
|
||||
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 RuntimeException if no database connection can be made using the URL.
|
||||
* @return DbConnectionInfo Database connection info.
|
||||
*/
|
||||
public static function parse(string $dsn): DbConnectionInfo {
|
||||
$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 RuntimeException if no database connection can be made using the URL.
|
||||
* @return DbConnection An active database connection.
|
||||
*/
|
||||
public static function create(string $dsn): DbConnection {
|
||||
$uri = self::parseDsnUri($dsn);
|
||||
$backend = self::resolveBackend($uri);
|
||||
return $backend->createConnection($backend->parseDsn($uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction wrapper.
|
||||
*
|
||||
|
|
11
src/Db/MariaDb/_ndx.php
Normal file
11
src/Db/MariaDb/_ndx.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// _ndx.php
|
||||
// Created: 2024-10-18
|
||||
// Updated: 2024-10-18
|
||||
|
||||
namespace Index\Db\MariaDb;
|
||||
|
||||
use Index\Db\DbBackends;
|
||||
|
||||
DbBackends::register('mariadb', MariaDbBackend::class);
|
||||
DbBackends::register('mysql', MariaDbBackend::class);
|
10
src/Db/NullDb/_ndx.php
Normal file
10
src/Db/NullDb/_ndx.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
// _ndx.php
|
||||
// Created: 2024-10-18
|
||||
// Updated: 2024-10-18
|
||||
|
||||
namespace Index\Db\NullDb;
|
||||
|
||||
use Index\Db\DbBackends;
|
||||
|
||||
DbBackends::register('null', NullDbBackend::class);
|
11
src/Db/Sqlite/_ndx.php
Normal file
11
src/Db/Sqlite/_ndx.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// _ndx.php
|
||||
// Created: 2024-10-18
|
||||
// Updated: 2024-10-18
|
||||
|
||||
namespace Index\Db\Sqlite;
|
||||
|
||||
use Index\Db\DbBackends;
|
||||
|
||||
DbBackends::register('sqlite', SqliteBackend::class);
|
||||
DbBackends::register('sqlite3', SqliteBackend::class);
|
|
@ -1,19 +1,19 @@
|
|||
<?php
|
||||
// CacheToolsTest.php
|
||||
// CacheBackendsTest.php
|
||||
// Created: 2024-04-10
|
||||
// Updated: 2024-10-16
|
||||
// Updated: 2024-10-18
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\{CoversClass,UsesClass};
|
||||
use Index\Cache\CacheTools;
|
||||
use Index\Cache\CacheBackends;
|
||||
use Index\Cache\ArrayCache\{ArrayCacheBackend,ArrayCacheProvider};
|
||||
use Index\Cache\Memcached\MemcachedBackend;
|
||||
use Index\Cache\Valkey\ValkeyBackend;
|
||||
use Index\Net\{DnsEndPoint,IpEndPoint,UnixEndPoint};
|
||||
|
||||
#[CoversClass(CacheTools::class)]
|
||||
#[CoversClass(CacheBackends::class)]
|
||||
#[CoversClass(ArrayCacheBackend::class)]
|
||||
#[CoversClass(ArrayCacheProvider::class)]
|
||||
#[CoversClass(MemcachedBackend::class)]
|
||||
|
@ -21,9 +21,9 @@ use Index\Net\{DnsEndPoint,IpEndPoint,UnixEndPoint};
|
|||
#[UsesClass(DnsEndPoint::class)]
|
||||
#[UsesClass(IpEndPoint::class)]
|
||||
#[UsesClass(UnixEndPoint::class)]
|
||||
final class CacheToolsTest extends TestCase {
|
||||
final class CacheBackendsTest extends TestCase {
|
||||
public function testBasicDSN(): void {
|
||||
$arrayCache = CacheTools::create('array:');
|
||||
$arrayCache = CacheBackends::create('array:');
|
||||
$this->assertInstanceOf(ArrayCacheProvider::class, $arrayCache);
|
||||
}
|
||||
|
116
tests/DbBackendsTest.php
Normal file
116
tests/DbBackendsTest.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
// DbBackendsTest.php
|
||||
// Created: 2021-04-28
|
||||
// Updated: 2024-10-16
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\{CoversClass,UsesClass};
|
||||
use Index\Db\DbBackends;
|
||||
use Index\Db\MariaDb\MariaDbBackend;
|
||||
use Index\Db\NullDb\NullDbConnection;
|
||||
use Index\Db\Sqlite\SqliteBackend;
|
||||
|
||||
#[CoversClass(DbBackends::class)]
|
||||
#[CoversClass(NullDbConnection::class)]
|
||||
#[CoversClass(MariaDbBackend::class)]
|
||||
#[CoversClass(SqliteBackend::class)]
|
||||
final class DbBackendsTest extends TestCase {
|
||||
public function testDSN(): void {
|
||||
$nullDbConn1 = DbBackends::create('null:');
|
||||
$this->assertInstanceOf(NullDbConnection::class, $nullDbConn1);
|
||||
|
||||
$maria = new MariaDbBackend;
|
||||
$sqlite = new SqliteBackend;
|
||||
|
||||
// flashii style misuzu connections details
|
||||
$mci1 = $maria->parseDsn('mariadb://flashii:TiFGzCTxzx0n2HYzmNZpa98j255X7W4B@:unix:/flashii/misuzu?socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4&init=SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\'');
|
||||
|
||||
$this->assertTrue($mci1->isUnixSocket());
|
||||
$this->assertEquals('/var/run/mysqld/mysqld.sock', (string)$mci1->getEndPoint());
|
||||
$this->assertEquals('', $mci1->getHost());
|
||||
$this->assertEquals(-1, $mci1->getPort());
|
||||
$this->assertEquals('/var/run/mysqld/mysqld.sock', $mci1->getSocketPath());
|
||||
$this->assertEquals('flashii', $mci1->getUserName());
|
||||
$this->assertEquals('TiFGzCTxzx0n2HYzmNZpa98j255X7W4B', $mci1->getPassword());
|
||||
$this->assertEquals('flashii_misuzu', $mci1->getDatabaseName());
|
||||
$this->assertTrue($mci1->hasCharacterSet());
|
||||
$this->assertEquals('utf8mb4', $mci1->getCharacterSet());
|
||||
$this->assertTrue($mci1->hasInitCommand());
|
||||
$this->assertEquals('SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\'', $mci1->getInitCommand());
|
||||
$this->assertFalse($mci1->isSecure());
|
||||
$this->assertNull($mci1->getKeyPath());
|
||||
$this->assertNull($mci1->getCertificatePath());
|
||||
$this->assertNull($mci1->getCertificateAuthorityPath());
|
||||
$this->assertNull($mci1->getTrustedCertificatesPath());
|
||||
$this->assertNull($mci1->getCipherAlgorithms());
|
||||
$this->assertTrue($mci1->shouldVerifyCertificate());
|
||||
$this->assertFalse($mci1->shouldUseCompression());
|
||||
|
||||
// generic ipv4
|
||||
$mci2 = $maria->parseDsn('mariadb://username:password@127.0.0.1/database');
|
||||
$this->assertEquals('127.0.0.1', (string)$mci2->getEndPoint());
|
||||
$this->assertEquals('127.0.0.1', $mci2->getHost());
|
||||
$this->assertEquals(3306, $mci2->getPort());
|
||||
$this->assertEquals('', $mci2->getSocketPath());
|
||||
$this->assertEquals('username', $mci2->getUserName());
|
||||
$this->assertEquals('password', $mci2->getPassword());
|
||||
$this->assertEquals('database', $mci2->getDatabaseName());
|
||||
$this->assertFalse($mci2->hasCharacterSet());
|
||||
$this->assertNull($mci2->getCharacterSet());
|
||||
$this->assertFalse($mci2->hasInitCommand());
|
||||
$this->assertNull($mci2->getInitCommand());
|
||||
$this->assertFalse($mci2->isSecure());
|
||||
$this->assertNull($mci2->getKeyPath());
|
||||
$this->assertNull($mci2->getCertificatePath());
|
||||
$this->assertNull($mci2->getCertificateAuthorityPath());
|
||||
$this->assertNull($mci2->getTrustedCertificatesPath());
|
||||
$this->assertNull($mci2->getCipherAlgorithms());
|
||||
$this->assertTrue($mci2->shouldVerifyCertificate());
|
||||
$this->assertFalse($mci2->shouldUseCompression());
|
||||
|
||||
// generic ipv6 with port
|
||||
$mci3 = $maria->parseDsn('mysql://username:password@[::1]:9001/database');
|
||||
$this->assertEquals('[::1]:9001', (string)$mci3->getEndPoint());
|
||||
$this->assertEquals('::1', $mci3->getHost());
|
||||
$this->assertEquals(9001, $mci3->getPort());
|
||||
$this->assertEquals('', $mci3->getSocketPath());
|
||||
$this->assertEquals('username', $mci3->getUserName());
|
||||
$this->assertEquals('password', $mci3->getPassword());
|
||||
$this->assertEquals('database', $mci3->getDatabaseName());
|
||||
$this->assertFalse($mci3->hasCharacterSet());
|
||||
$this->assertNull($mci3->getCharacterSet());
|
||||
$this->assertFalse($mci3->hasInitCommand());
|
||||
$this->assertNull($mci3->getInitCommand());
|
||||
$this->assertFalse($mci3->isSecure());
|
||||
$this->assertNull($mci3->getKeyPath());
|
||||
$this->assertNull($mci3->getCertificatePath());
|
||||
$this->assertNull($mci3->getCertificateAuthorityPath());
|
||||
$this->assertNull($mci3->getTrustedCertificatesPath());
|
||||
$this->assertNull($mci3->getCipherAlgorithms());
|
||||
$this->assertTrue($mci3->shouldVerifyCertificate());
|
||||
$this->assertFalse($mci3->shouldUseCompression());
|
||||
|
||||
// sqlite normal
|
||||
$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&readOnly');
|
||||
$this->assertEquals('', $sql1->getFileName());
|
||||
$this->assertEquals('Y63vrCttK8bEdPUIXVCOiLQKIgdbUn8V', $sql1->getEncryptionKey());
|
||||
$this->assertTrue($sql1->shouldReadOnly());
|
||||
$this->assertTrue($sql1->shouldCreate());
|
||||
|
||||
// sqlite memory
|
||||
$sql1 = $sqlite->parseDsn('sqlite::memory:');
|
||||
$this->assertEquals(':memory:', $sql1->getFileName());
|
||||
$this->assertEquals('', $sql1->getEncryptionKey());
|
||||
$this->assertFalse($sql1->shouldReadOnly());
|
||||
$this->assertTrue($sql1->shouldCreate());
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
|
|||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use Index\Config\{GetValueInfoTrait,GetValuesTrait,MutableConfigTrait};
|
||||
use Index\Config\Db\{DbConfig,DbConfigValueInfo};
|
||||
use Index\Db\{DbConnection,DbTools};
|
||||
use Index\Db\{DbBackends,DbConnection};
|
||||
|
||||
#[CoversClass(DbConfig::class)]
|
||||
#[CoversClass(DbConfigValueInfo::class)]
|
||||
|
@ -43,7 +43,7 @@ final class DbConfigTest extends TestCase {
|
|||
];
|
||||
|
||||
protected function setUp(): void {
|
||||
$this->dbConn = DbTools::create('sqlite::memory:');
|
||||
$this->dbConn = DbBackends::create('sqlite::memory:');
|
||||
$this->dbConn->execute('CREATE TABLE skh_config (config_name TEXT NOT NULL COLLATE NOCASE, config_value BLOB NOT NULL, PRIMARY KEY (config_name))');
|
||||
$this->dbConn->execute('CREATE TABLE skh_user_settings (user_id INTEGER NOT NULL, setting_name TEXT NOT NULL COLLATE NOCASE, setting_value BLOB NOT NULL, PRIMARY KEY (user_id, setting_name))');
|
||||
|
||||
|
|
|
@ -6,17 +6,11 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\{CoversClass,UsesClass};
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use Index\Db\{DbTools,DbType};
|
||||
use Index\Db\MariaDb\MariaDbBackend;
|
||||
use Index\Db\NullDb\NullDbConnection;
|
||||
use Index\Db\Sqlite\SqliteBackend;
|
||||
|
||||
#[CoversClass(DbTools::class)]
|
||||
#[CoversClass(DbType::class)]
|
||||
#[CoversClass(NullDbConnection::class)]
|
||||
#[CoversClass(MariaDbBackend::class)]
|
||||
#[CoversClass(SqliteBackend::class)]
|
||||
final class DbToolsTest extends TestCase {
|
||||
public function testDetectType(): void {
|
||||
$this->assertEquals(DbType::NULL, DbTools::detectType(null));
|
||||
|
@ -33,101 +27,4 @@ final class DbToolsTest extends TestCase {
|
|||
|
||||
$this->assertEquals(DbType::BLOB, DbTools::detectType($blob));
|
||||
}
|
||||
|
||||
public function testDSN(): void {
|
||||
$nullDbConn1 = DbTools::create('null:');
|
||||
$this->assertInstanceOf(NullDbConnection::class, $nullDbConn1);
|
||||
|
||||
$maria = new MariaDbBackend;
|
||||
$sqlite = new SqliteBackend;
|
||||
|
||||
// flashii style misuzu connections details
|
||||
$mci1 = $maria->parseDsn('mariadb://flashii:TiFGzCTxzx0n2HYzmNZpa98j255X7W4B@:unix:/flashii/misuzu?socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4&init=SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\'');
|
||||
|
||||
$this->assertTrue($mci1->isUnixSocket());
|
||||
$this->assertEquals('/var/run/mysqld/mysqld.sock', (string)$mci1->getEndPoint());
|
||||
$this->assertEquals('', $mci1->getHost());
|
||||
$this->assertEquals(-1, $mci1->getPort());
|
||||
$this->assertEquals('/var/run/mysqld/mysqld.sock', $mci1->getSocketPath());
|
||||
$this->assertEquals('flashii', $mci1->getUserName());
|
||||
$this->assertEquals('TiFGzCTxzx0n2HYzmNZpa98j255X7W4B', $mci1->getPassword());
|
||||
$this->assertEquals('flashii_misuzu', $mci1->getDatabaseName());
|
||||
$this->assertTrue($mci1->hasCharacterSet());
|
||||
$this->assertEquals('utf8mb4', $mci1->getCharacterSet());
|
||||
$this->assertTrue($mci1->hasInitCommand());
|
||||
$this->assertEquals('SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\'', $mci1->getInitCommand());
|
||||
$this->assertFalse($mci1->isSecure());
|
||||
$this->assertNull($mci1->getKeyPath());
|
||||
$this->assertNull($mci1->getCertificatePath());
|
||||
$this->assertNull($mci1->getCertificateAuthorityPath());
|
||||
$this->assertNull($mci1->getTrustedCertificatesPath());
|
||||
$this->assertNull($mci1->getCipherAlgorithms());
|
||||
$this->assertTrue($mci1->shouldVerifyCertificate());
|
||||
$this->assertFalse($mci1->shouldUseCompression());
|
||||
|
||||
// generic ipv4
|
||||
$mci2 = $maria->parseDsn('mariadb://username:password@127.0.0.1/database');
|
||||
$this->assertEquals('127.0.0.1', (string)$mci2->getEndPoint());
|
||||
$this->assertEquals('127.0.0.1', $mci2->getHost());
|
||||
$this->assertEquals(3306, $mci2->getPort());
|
||||
$this->assertEquals('', $mci2->getSocketPath());
|
||||
$this->assertEquals('username', $mci2->getUserName());
|
||||
$this->assertEquals('password', $mci2->getPassword());
|
||||
$this->assertEquals('database', $mci2->getDatabaseName());
|
||||
$this->assertFalse($mci2->hasCharacterSet());
|
||||
$this->assertNull($mci2->getCharacterSet());
|
||||
$this->assertFalse($mci2->hasInitCommand());
|
||||
$this->assertNull($mci2->getInitCommand());
|
||||
$this->assertFalse($mci2->isSecure());
|
||||
$this->assertNull($mci2->getKeyPath());
|
||||
$this->assertNull($mci2->getCertificatePath());
|
||||
$this->assertNull($mci2->getCertificateAuthorityPath());
|
||||
$this->assertNull($mci2->getTrustedCertificatesPath());
|
||||
$this->assertNull($mci2->getCipherAlgorithms());
|
||||
$this->assertTrue($mci2->shouldVerifyCertificate());
|
||||
$this->assertFalse($mci2->shouldUseCompression());
|
||||
|
||||
// generic ipv6 with port
|
||||
$mci3 = $maria->parseDsn('mysql://username:password@[::1]:9001/database');
|
||||
$this->assertEquals('[::1]:9001', (string)$mci3->getEndPoint());
|
||||
$this->assertEquals('::1', $mci3->getHost());
|
||||
$this->assertEquals(9001, $mci3->getPort());
|
||||
$this->assertEquals('', $mci3->getSocketPath());
|
||||
$this->assertEquals('username', $mci3->getUserName());
|
||||
$this->assertEquals('password', $mci3->getPassword());
|
||||
$this->assertEquals('database', $mci3->getDatabaseName());
|
||||
$this->assertFalse($mci3->hasCharacterSet());
|
||||
$this->assertNull($mci3->getCharacterSet());
|
||||
$this->assertFalse($mci3->hasInitCommand());
|
||||
$this->assertNull($mci3->getInitCommand());
|
||||
$this->assertFalse($mci3->isSecure());
|
||||
$this->assertNull($mci3->getKeyPath());
|
||||
$this->assertNull($mci3->getCertificatePath());
|
||||
$this->assertNull($mci3->getCertificateAuthorityPath());
|
||||
$this->assertNull($mci3->getTrustedCertificatesPath());
|
||||
$this->assertNull($mci3->getCipherAlgorithms());
|
||||
$this->assertTrue($mci3->shouldVerifyCertificate());
|
||||
$this->assertFalse($mci3->shouldUseCompression());
|
||||
|
||||
// sqlite normal
|
||||
$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&readOnly');
|
||||
$this->assertEquals('', $sql1->getFileName());
|
||||
$this->assertEquals('Y63vrCttK8bEdPUIXVCOiLQKIgdbUn8V', $sql1->getEncryptionKey());
|
||||
$this->assertTrue($sql1->shouldReadOnly());
|
||||
$this->assertTrue($sql1->shouldCreate());
|
||||
|
||||
// sqlite memory
|
||||
$sql1 = $sqlite->parseDsn('sqlite::memory:');
|
||||
$this->assertEquals(':memory:', $sql1->getFileName());
|
||||
$this->assertEquals('', $sql1->getEncryptionKey());
|
||||
$this->assertFalse($sql1->shouldReadOnly());
|
||||
$this->assertTrue($sql1->shouldCreate());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue