From d9ffc36fe6d07ff1927a09fe9fbec1ea647e1d41 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Fri, 7 Jun 2024 20:57:12 -0400 Subject: [PATCH] Use PDO singleton --- src/Configuration.php | 34 ++++++++++++++++++++-------------- src/Count.php | 12 ++++-------- src/Custom.php | 22 +++++++--------------- src/Definition.php | 14 +++++--------- src/Delete.php | 13 ++++--------- src/Document.php | 17 ++++++----------- src/DocumentList.php | 8 +++----- src/Exists.php | 12 ++++-------- src/Patch.php | 12 ++++-------- src/RemoveFields.php | 12 ++++-------- 10 files changed, 61 insertions(+), 95 deletions(-) diff --git a/src/Configuration.php b/src/Configuration.php index e214aa8..b562e7b 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -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,21 +38,24 @@ class Configuration */ public static function dbConn(): 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, - $_ENV['PDO_DOC_PASSWORD'] ?? self::$password, self::$options); + if (is_null(self::$_pdo)) { + if (empty(self::$pdoDSN)) { + throw new DocumentException('Please provide a data source name (DSN) before attempting data access'); + } + 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); - self::$mode = match ($driver) { - 'pgsql' => Mode::PgSQL, - 'sqlite' => Mode::SQLite, - default => throw new DocumentException( - "Unsupported driver $driver: this library currently supports PostgreSQL and SQLite") - }; + if (is_null(self::$mode)) { + $driver = self::$_pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + self::$mode = match ($driver) { + 'pgsql' => Mode::PgSQL, + 'sqlite' => Mode::SQLite, + default => throw new DocumentException( + "Unsupported driver $driver: this library currently supports PostgreSQL and SQLite") + }; + } } - return $db; + + return self::$_pdo; } } diff --git a/src/Count.php b/src/Count.php index ca945b2..df91c72 100644 --- a/src/Count.php +++ b/src/Count.php @@ -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()); } } diff --git a/src/Custom.php b/src/Custom.php index fc1a068..5ba6663 100644 --- a/src/Custom.php +++ b/src/Custom.php @@ -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 "
Binding $value to $key\n
"; $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 $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 $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; } } } diff --git a/src/Definition.php b/src/Definition.php index 85b0169..be87c3a 100644 --- a/src/Definition.php +++ b/src/Definition.php @@ -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), []); } } diff --git a/src/Delete.php b/src/Delete.php index 477bb26..00a408f 100644 --- a/src/Delete.php +++ b/src/Delete.php @@ -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, [])); } } diff --git a/src/Document.php b/src/Document.php index e6c209d..5f14323 100644 --- a/src/Document.php +++ b/src/Document.php @@ -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))); } } diff --git a/src/DocumentList.php b/src/DocumentList.php index 60a7658..44ec060 100644 --- a/src/DocumentList.php +++ b/src/DocumentList.php @@ -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 $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; } } diff --git a/src/Exists.php b/src/Exists.php index 5b4e6cd..11548d4 100644 --- a/src/Exists.php +++ b/src/Exists.php @@ -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()); } } diff --git a/src/Patch.php b/src/Patch.php index 9bbbaa9..98e5378 100644 --- a/src/Patch.php +++ b/src/Patch.php @@ -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))); } } diff --git a/src/RemoveFields.php b/src/RemoveFields.php index 4432c0f..b32e2f4 100644 --- a/src/RemoveFields.php +++ b/src/RemoveFields.php @@ -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)); } }