Use PDO singleton

This commit is contained in:
Daniel J. Summers 2024-06-07 20:57:12 -04:00
parent bcca9f5ace
commit d9ffc36fe6
10 changed files with 61 additions and 95 deletions

View File

@ -27,6 +27,9 @@ class Configuration
/** @var Mode|null The mode in which the library is operating (filled after first connection if not configured) */
public static ?Mode $mode = null;
/** @var PDO|null The PDO instance to use for database commands */
private static ?PDO $_pdo = null;
/**
* Retrieve a new connection to the database
*
@ -35,14 +38,15 @@ class Configuration
*/
public static function dbConn(): PDO
{
if (is_null(self::$_pdo)) {
if (empty(self::$pdoDSN)) {
throw new DocumentException('Please provide a data source name (DSN) before attempting data access');
}
$db = new PDO(self::$pdoDSN, $_ENV['PDO_DOC_USERNAME'] ?? self::$username,
self::$_pdo = new PDO(self::$pdoDSN, $_ENV['PDO_DOC_USERNAME'] ?? self::$username,
$_ENV['PDO_DOC_PASSWORD'] ?? self::$password, self::$options);
if (is_null(self::$mode)) {
$driver = $db->getAttribute(PDO::ATTR_DRIVER_NAME);
$driver = self::$_pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
self::$mode = match ($driver) {
'pgsql' => Mode::PgSQL,
'sqlite' => Mode::SQLite,
@ -50,6 +54,8 @@ class Configuration
"Unsupported driver $driver: this library currently supports PostgreSQL and SQLite")
};
}
return $db;
}
return self::$_pdo;
}
}

View File

@ -3,7 +3,6 @@
namespace BitBadger\PDODocument;
use BitBadger\PDODocument\Mapper\CountMapper;
use PDO;
/**
* Functions to count documents
@ -14,13 +13,12 @@ class Count
* Count all documents in a table
*
* @param string $tableName The name of the table in which documents should be counted
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @return int The count of documents in the table
* @throws DocumentException If one is encountered
*/
public static function all(string $tableName, ?PDO $pdo = null): int
public static function all(string $tableName): int
{
return Custom::scalar(Query\Count::all($tableName), [], new CountMapper(), $pdo);
return Custom::scalar(Query\Count::all($tableName), [], new CountMapper());
}
/**
@ -28,16 +26,14 @@ class Count
*
* @param string $tableName The name of the table in which documents should be counted
* @param array|Field[] $fields The field comparison to match
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @return int The count of documents matching the field comparison
* @throws DocumentException If one is encountered
*/
public static function byFields(string $tableName, array $fields, ?PDO $pdo = null,
string $conjunction = 'AND'): int
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): int
{
$namedFields = Parameters::nameFields($fields);
return Custom::scalar(Query\Count::byFields($tableName, $namedFields, $conjunction),
Parameters::addFields($namedFields, []), new CountMapper(), $pdo);
Parameters::addFields($namedFields, []), new CountMapper());
}
}

View File

@ -16,14 +16,13 @@ class Custom
*
* @param string $query The query to be run
* @param array $parameters The parameters for the query
* @param PDO $pdo The database connection on which the query should be run
* @return PDOStatement The result of executing the query
* @throws DocumentException If the query execution is unsuccessful
*/
public static function runQuery(string $query, array $parameters, PDO $pdo): PDOStatement
public static function &runQuery(string $query, array $parameters): PDOStatement
{
$debug = defined('PDO_DOC_DEBUG_SQL');
$stmt = $pdo->prepare($query);
$stmt = Configuration::dbConn()->prepare($query);
foreach ($parameters as $key => $value) {
if ($debug) echo "<pre>Binding $value to $key\n</pre>";
$dataType = match (true) {
@ -77,19 +76,16 @@ class Custom
* @param string $query The query to be executed (will have "LIMIT 1" appended)
* @param array $parameters Parameters to use in executing the query
* @param Mapper<TDoc> $mapper Mapper to deserialize the result
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @return false|TDoc The item if it is found, false if not
* @throws DocumentException If any is encountered
*/
public static function single(string $query, array $parameters, Mapper $mapper, ?PDO $pdo = null): mixed
public static function single(string $query, array $parameters, Mapper $mapper): mixed
{
try {
$stmt = self::runQuery("$query LIMIT 1", $parameters,
is_null($pdo) ? $actualPDO = Configuration::dbConn() : $pdo);
$stmt = &self::runQuery("$query LIMIT 1", $parameters);
return ($first = $stmt->fetch(PDO::FETCH_ASSOC)) ? $mapper->map($first) : false;
} finally {
$stmt = null;
if (isset($actualPDO)) $actualPDO = null;
}
}
@ -98,16 +94,14 @@ class Custom
*
* @param string $query The query to execute
* @param array $parameters Parameters to use in executing the query
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @throws DocumentException If any is encountered
*/
public static function nonQuery(string $query, array $parameters, ?PDO $pdo = null): void
public static function nonQuery(string $query, array $parameters): void
{
try {
$stmt = self::runQuery($query, $parameters, is_null($pdo) ? $actualPDO = Configuration::dbConn() : $pdo);
$stmt = &self::runQuery($query, $parameters);
} finally {
$stmt = null;
if (isset($actualPDO)) $actualPDO = null;
}
}
@ -118,18 +112,16 @@ class Custom
* @param string $query The query to retrieve the value
* @param array $parameters Parameters to use in executing the query
* @param Mapper<T> $mapper The mapper to obtain the result
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @return mixed|false|T The scalar value if found, false if not
* @throws DocumentException If any is encountered
*/
public static function scalar(string $query, array $parameters, Mapper $mapper, ?PDO $pdo = null): mixed
{
try {
$stmt = self::runQuery($query, $parameters, is_null($pdo) ? $actualPDO = Configuration::dbConn() : $pdo);
$stmt = &self::runQuery($query, $parameters);
return ($first = $stmt->fetch(PDO::FETCH_NUM)) ? $mapper->map($first) : false;
} finally {
$stmt = null;
if (isset($actualPDO)) $actualPDO = null;
}
}
}

View File

@ -2,8 +2,6 @@
namespace BitBadger\PDODocument;
use PDO;
/**
* Functions to create tables and indexes
*/
@ -13,13 +11,12 @@ class Definition
* Ensure a document table exists
*
* @param string $name The name of the table to be created if it does not exist
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @throws DocumentException If any is encountered
*/
public static function ensureTable(string $name, ?PDO $pdo = null): void
public static function ensureTable(string $name): void
{
Custom::nonQuery(Query\Definition::ensureTable($name), [], $pdo);
Custom::nonQuery(Query\Definition::ensureKey($name), [], $pdo);
Custom::nonQuery(Query\Definition::ensureTable($name), []);
Custom::nonQuery(Query\Definition::ensureKey($name), []);
}
/**
@ -28,11 +25,10 @@ class Definition
* @param string $tableName The name of the table which should be indexed
* @param string $indexName The name of the index
* @param array $fields Fields which should be a part of this index
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @throws DocumentException If any is encountered
*/
public static function ensureFieldIndex(string $tableName, string $indexName, array $fields, ?PDO $pdo = null): void
public static function ensureFieldIndex(string $tableName, string $indexName, array $fields): void
{
Custom::nonQuery(Query\Definition::ensureIndexOn($tableName, $indexName, $fields), [], $pdo);
Custom::nonQuery(Query\Definition::ensureIndexOn($tableName, $indexName, $fields), []);
}
}

View File

@ -2,8 +2,6 @@
namespace BitBadger\PDODocument;
use PDO;
/**
* Functions to delete documents
*/
@ -14,12 +12,11 @@ class Delete
*
* @param string $tableName The table from which the document should be deleted
* @param mixed $docId The ID of the document to be deleted
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @throws DocumentException If any is encountered
*/
public static function byId(string $tableName, mixed $docId, ?PDO $pdo = null): void
public static function byId(string $tableName, mixed $docId): void
{
Custom::nonQuery(Query\Delete::byId($tableName), Parameters::id($docId), $pdo);
Custom::nonQuery(Query\Delete::byId($tableName), Parameters::id($docId));
}
/**
@ -27,15 +24,13 @@ class Delete
*
* @param string $tableName The table from which documents should be deleted
* @param array|Field[] $fields The field comparison to match
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @throws DocumentException If any is encountered
*/
public static function byFields(string $tableName, array $fields, ?PDO $pdo = null,
string $conjunction = 'AND'): void
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): void
{
$namedFields = Parameters::nameFields($fields);
Custom::nonQuery(Query\Delete::byFields($tableName, $namedFields, $conjunction),
Parameters::addFields($namedFields, []), $pdo);
Parameters::addFields($namedFields, []));
}
}

View File

@ -2,8 +2,6 @@
namespace BitBadger\PDODocument;
use PDO;
/**
* Functions that apply at a whole document level
*/
@ -14,12 +12,11 @@ class Document
*
* @param string $tableName The name of the table into which the document should be inserted
* @param array|object $document The document to be inserted
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @throws DocumentException If any is encountered
*/
public static function insert(string $tableName, array|object $document, ?PDO $pdo = null): void
public static function insert(string $tableName, array|object $document): void
{
Custom::nonQuery(Query::insert($tableName), Parameters::json(':data', $document), $pdo);
Custom::nonQuery(Query::insert($tableName), Parameters::json(':data', $document));
}
/**
@ -27,12 +24,11 @@ class Document
*
* @param string $tableName The name of the table to which the document should be saved
* @param array|object $document The document to be saved
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @throws DocumentException If any is encountered
*/
public static function save(string $tableName, array|object $document, ?PDO $pdo = null): void
public static function save(string $tableName, array|object $document): void
{
Custom::nonQuery(Query::save($tableName), Parameters::json(':data', $document), $pdo);
Custom::nonQuery(Query::save($tableName), Parameters::json(':data', $document));
}
/**
@ -41,12 +37,11 @@ class Document
* @param string $tableName The table in which the document should be updated
* @param mixed $docId The ID of the document to be updated
* @param array|object $document The document to be updated
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @throws DocumentException If any is encountered
*/
public static function update(string $tableName, mixed $docId, array|object $document, ?PDO $pdo = null): void
public static function update(string $tableName, mixed $docId, array|object $document): void
{
Custom::nonQuery(Query::update($tableName),
array_merge(Parameters::id($docId), Parameters::json(':data', $document)), $pdo);
array_merge(Parameters::id($docId), Parameters::json(':data', $document)));
}
}

View File

@ -18,11 +18,10 @@ class DocumentList
/**
* Constructor
*
* @param PDO|null $pdo The database connection against which the query was opened
* @param PDOStatement|null $result The result of the query
* @param Mapper<TDoc> $mapper The mapper to deserialize JSON
*/
private function __construct(private ?PDO $pdo, private ?PDOStatement $result, private Mapper $mapper) { }
private function __construct(private ?PDOStatement &$result, private readonly Mapper $mapper) { }
/**
* Construct a new document list
@ -35,8 +34,8 @@ class DocumentList
*/
public static function create(string $query, array $parameters, Mapper $mapper): static
{
$pdo = Configuration::dbConn();
return new static($pdo, Custom::runQuery($query, $parameters, $pdo), $mapper);
$stmt = &Custom::runQuery($query, $parameters);
return new static($stmt, $mapper);
}
/**
@ -50,6 +49,5 @@ class DocumentList
while ($row = $this->result->fetch(PDO::FETCH_ASSOC)) yield $this->mapper->map($row);
}
$this->result = null;
$this->pdo = null;
}
}

View File

@ -3,7 +3,6 @@
namespace BitBadger\PDODocument;
use BitBadger\PDODocument\Mapper\ExistsMapper;
use PDO;
/**
* Functions to determine if documents exist
@ -15,13 +14,12 @@ class Exists
*
* @param string $tableName The name of the table in which document existence should be determined
* @param mixed $docId The ID of the document whose existence should be determined
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @return bool True if the document exists, false if not
* @throws DocumentException If any is encountered
*/
public static function byId(string $tableName, mixed $docId, ?PDO $pdo = null): bool
public static function byId(string $tableName, mixed $docId): bool
{
return Custom::scalar(Query\Exists::byId($tableName), Parameters::id($docId), new ExistsMapper(), $pdo);
return Custom::scalar(Query\Exists::byId($tableName), Parameters::id($docId), new ExistsMapper());
}
/**
@ -29,16 +27,14 @@ class Exists
*
* @param string $tableName The name of the table in which document existence should be determined
* @param Field[] $fields The field comparison to match
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @return bool True if any documents match the field comparison, false if not
* @throws DocumentException If any is encountered
*/
public static function byFields(string $tableName, array $fields, ?PDO $pdo = null,
string $conjunction = 'AND'): bool
public static function byFields(string $tableName, array $fields, string $conjunction = 'AND'): bool
{
$namedFields = Parameters::nameFields($fields);
return Custom::scalar(Query\Exists::byFields($tableName, $namedFields, $conjunction),
Parameters::addFields($namedFields, []), new ExistsMapper(), $pdo);
Parameters::addFields($namedFields, []), new ExistsMapper());
}
}

View File

@ -2,8 +2,6 @@
namespace BitBadger\PDODocument;
use PDO;
/**
* Functions to patch (partially update) documents
*/
@ -15,13 +13,12 @@ class Patch
* @param string $tableName The table in which the document should be patched
* @param mixed $docId The ID of the document to be patched
* @param array|object $patch The object with which the document should be patched (will be JSON-encoded)
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @throws DocumentException If any is encountered (database mode must be set)
*/
public static function byId(string $tableName, mixed $docId, array|object $patch, ?PDO $pdo = null): void
public static function byId(string $tableName, mixed $docId, array|object $patch): void
{
Custom::nonQuery(Query\Patch::byId($tableName),
array_merge(Parameters::id($docId), Parameters::json(':data', $patch)), $pdo);
array_merge(Parameters::id($docId), Parameters::json(':data', $patch)));
}
/**
@ -30,15 +27,14 @@ class Patch
* @param string $tableName The table in which documents should be patched
* @param array|Field[] $fields The field comparison to match
* @param array|object $patch The object with which the documents should be patched (will be JSON-encoded)
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @throws DocumentException If any is encountered
*/
public static function byFields(string $tableName, array $fields, array|object $patch, ?PDO $pdo = null,
public static function byFields(string $tableName, array $fields, array|object $patch,
string $conjunction = 'AND'): void
{
$namedFields = Parameters::nameFields($fields);
Custom::nonQuery(Query\Patch::byFields($tableName, $namedFields, $conjunction),
Parameters::addFields($namedFields, Parameters::json(':data', $patch)), $pdo);
Parameters::addFields($namedFields, Parameters::json(':data', $patch)));
}
}

View File

@ -2,8 +2,6 @@
namespace BitBadger\PDODocument;
use PDO;
/**
* Functions to remove fields from documents
*/
@ -15,14 +13,13 @@ class RemoveFields
* @param string $tableName The table in which the document should have fields removed
* @param mixed $docId The ID of the document from which fields should be removed
* @param array|string[] $fieldNames The names of the fields to be removed
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @throws DocumentException If any is encountered
*/
public static function byId(string $tableName, mixed $docId, array $fieldNames, ?PDO $pdo = null): void
public static function byId(string $tableName, mixed $docId, array $fieldNames): void
{
$nameParams = Parameters::fieldNames(':name', $fieldNames);
Custom::nonQuery(Query\RemoveFields::byId($tableName, $nameParams),
array_merge(Parameters::id($docId), $nameParams), $pdo);
array_merge(Parameters::id($docId), $nameParams));
}
/**
@ -31,16 +28,15 @@ class RemoveFields
* @param string $tableName The table in which documents should have fields removed
* @param array|Field[] $fields The field comparison to match
* @param array|string[] $fieldNames The names of the fields to be removed
* @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided)
* @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`)
* @throws DocumentException If any is encountered
*/
public static function byFields(string $tableName, array $fields, array $fieldNames, ?PDO $pdo = null,
public static function byFields(string $tableName, array $fields, array $fieldNames,
string $conjunction = 'AND'): void
{
$nameParams = Parameters::fieldNames(':name', $fieldNames);
$namedFields = Parameters::nameFields($fields);
Custom::nonQuery(Query\RemoveFields::byFields($tableName, $namedFields, $nameParams, $conjunction),
Parameters::addFields($namedFields, $nameParams), $pdo);
Parameters::addFields($namedFields, $nameParams));
}
}