WIP on PHP (Leaf) version
This commit is contained in:
454
src/app/documents/Document.php
Normal file
454
src/app/documents/Document.php
Normal file
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BitBadger\PgSQL\Documents;
|
||||
|
||||
use PDOStatement;
|
||||
|
||||
/** Document manipulation functions */
|
||||
class Document
|
||||
{
|
||||
/** JSON Mapper instance to use for creating a domain type instance from a document */
|
||||
private static ?\JsonMapper $mapper = null;
|
||||
|
||||
/**
|
||||
* Map a domain type from the JSON document retrieved
|
||||
*
|
||||
* @param string $columnName The name of the column from the database
|
||||
* @param array $result An associative array with a single result to be mapped
|
||||
* @param class-string<Type> $className The name of the class onto which the JSON will be mapped
|
||||
* @return Type The domain type
|
||||
*/
|
||||
public static function mapDocFromJson(string $columnName, array $result, string $className): mixed
|
||||
{
|
||||
if (is_null(Document::$mapper)) {
|
||||
Document::$mapper = new \JsonMapper();
|
||||
}
|
||||
|
||||
$mapped = new $className();
|
||||
Document::$mapper->map(json_decode($result[$columnName]), $mapped);
|
||||
return $mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a domain type from the JSON document retrieved
|
||||
*
|
||||
* @param array $result An associative array with a single result to be mapped
|
||||
* @param class-string<Type> $className The name of the class onto which the JSON will be mapped
|
||||
* @return Type The domain type
|
||||
*/
|
||||
public static function mapFromJson(array $result, string $className): mixed
|
||||
{
|
||||
return Document::mapDocFromJson('data', $result, $className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a document-focused statement that does not return results
|
||||
*
|
||||
* @param string $query The query to be executed
|
||||
* @param string $docId The ID of the document on which action should be taken
|
||||
* @param array|object $document The array or object representing the document
|
||||
*/
|
||||
private static function executeNonQuery(string $query, string $docId, array|object $document)
|
||||
{
|
||||
$nonQuery = pdo()->prepare($query);
|
||||
$nonQuery->bindParam('@id', $docId);
|
||||
$nonQuery->bindParam('@data', Query::jsonbDocParam($document));
|
||||
$nonQuery->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a document
|
||||
*
|
||||
* @param string $tableName The name of the table into which a document should be inserted
|
||||
* @param string $docId The ID of the document to be inserted
|
||||
* @param array|object $document The array or object representing the document
|
||||
*/
|
||||
public static function insert(string $tableName, string $docId, array|object $document)
|
||||
{
|
||||
Document::executeNonQuery(Query::insert($tableName), $docId, $document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save (upsert) a document
|
||||
*
|
||||
* @param string $tableName The name of the table into which a document should be inserted
|
||||
* @param string $docId The ID of the document to be inserted
|
||||
* @param array|object $document The array or object representing the document
|
||||
*/
|
||||
public static function save(string $tableName, string $docId, array|object $document)
|
||||
{
|
||||
Document::executeNonQuery(Query::save($tableName), $docId, $document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all documents in a table
|
||||
*
|
||||
* @param string $tableName The name of the table in which documents should be counted
|
||||
* @return int The number of documents in the table
|
||||
*/
|
||||
public static function countAll(string $tableName): int
|
||||
{
|
||||
$result = pdo()->query(Query::countAll($tableName))->fetch(\PDO::FETCH_ASSOC);
|
||||
return intval($result['it']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count documents in a table by JSON containment `@>`
|
||||
*
|
||||
* @param string $tableName The name of the table in which documents should be counted
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
* @return int The number of documents in the table matching the JSON containment query
|
||||
*/
|
||||
public static function countByContains(string $tableName, array|object $criteria): int
|
||||
{
|
||||
$query = pdo()->prepare(Query::countByContains($tableName));
|
||||
$query->bindParam('@criteria', Query::jsonbDocParam($criteria));
|
||||
$query->execute();
|
||||
$result = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
return intval($result['it']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count documents in a table by JSON Path match `@?`
|
||||
*
|
||||
* @param string $tableName The name of the table in which documents should be counted
|
||||
* @param string $jsonPath The JSON Path to be matched
|
||||
* @return int The number of documents in the table matching the JSON Path
|
||||
*/
|
||||
public static function countByJsonPath(string $tableName, string $jsonPath): int
|
||||
{
|
||||
$query = pdo()->prepare(Query::countByContains($tableName));
|
||||
$query->bindParam('@path', $jsonPath);
|
||||
$query->execute();
|
||||
$result = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
return intval($result['it']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a document exists for the given ID
|
||||
*
|
||||
* @param string $tableName The name of the table in which existence should be checked
|
||||
* @param string $docId The ID of the document whose existence should be checked
|
||||
* @return bool True if the document exists, false if not
|
||||
*/
|
||||
public static function existsById(string $tableName, string $docId): bool
|
||||
{
|
||||
$query = pdo()->prepare(Query::existsById($tableName));
|
||||
$query->bindParam('@id', $docId);
|
||||
$query->execute();
|
||||
$result = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
return boolval($result['it']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if documents exist by JSON containment `@>`
|
||||
*
|
||||
* @param string $tableName The name of the table in which existence should be checked
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
* @return int True if any documents in the table match the JSON containment query, false if not
|
||||
*/
|
||||
public static function existsByContains(string $tableName, array|object $criteria): bool
|
||||
{
|
||||
$query = pdo()->prepare(Query::existsByContains($tableName));
|
||||
$query->bindParam('@criteria', Query::jsonbDocParam($criteria));
|
||||
$query->execute();
|
||||
$result = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
return boolval($result['it']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if documents exist by JSON Path match `@?`
|
||||
*
|
||||
* @param string $tableName The name of the table in which existence should be checked
|
||||
* @param string $jsonPath The JSON Path to be matched
|
||||
* @return int True if any documents in the table match the JSON Path, false if not
|
||||
*/
|
||||
public static function existsByJsonPath(string $tableName, string $jsonPath): bool
|
||||
{
|
||||
$query = pdo()->prepare(Query::existsByJsonPath($tableName));
|
||||
$query->bindParam('@path', $jsonPath);
|
||||
$query->execute();
|
||||
$result = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
return boolval($result['it']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the results of a query to domain type objects
|
||||
*
|
||||
* @param \PDOStatement $stmt The statement with the query to be run
|
||||
* @param class-string<Type> $className The type of document to be mapped
|
||||
* @return array<Type> The documents matching the query
|
||||
*/
|
||||
private static function mapResults(\PDOStatement $stmt, string $className): array
|
||||
{
|
||||
return array_map(fn ($it) => Document::mapFromJson($it, $className), $stmt->fetchAll(\PDO::FETCH_ASSOC));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all documents in a table
|
||||
*
|
||||
* @param string $tableName The table from which all documents should be retrieved
|
||||
* @param class-string<Type> $className The type of document to be retrieved
|
||||
* @return array<Type> An array of documents
|
||||
*/
|
||||
public static function findAll(string $tableName, string $className): array
|
||||
{
|
||||
return Document::mapResults(pdo()->query(Query::selectFromTable($tableName)), $className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a document by its ID
|
||||
*
|
||||
* @param string $tableName The table from which a document should be retrieved
|
||||
* @param string $docId The ID of the document to retrieve
|
||||
* @param class-string<Type> $className The type of document to retrieve
|
||||
* @return Type|null The document, or null if it is not found
|
||||
*/
|
||||
public static function findById(string $tableName, string $docId, string $className): mixed
|
||||
{
|
||||
$query = pdo()->prepare(Query::findById($tableName));
|
||||
$query->bindParam(':id', $docId);
|
||||
$query->execute();
|
||||
$result = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
return $result ? Document::mapFromJson($result, $className) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSON containment query
|
||||
*
|
||||
* @param string $tableName The table from which documents should be retrieved
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
* @return \PDOStatement An executed query ready to be fetched
|
||||
*/
|
||||
private static function queryByContains(string $tableName, array|object $criteria): \PDOStatement
|
||||
{
|
||||
$query = pdo()->prepare(Query::findByContains($tableName));
|
||||
$query->bindParam('@criteria', Query::jsonbDocParam($criteria));
|
||||
$query->execute();
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents in a table via JSON containment `@>`
|
||||
*
|
||||
* @param string $tableName The table from which documents should be retrieved
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
* @param class-string<Type> $className The type of document to be retrieved
|
||||
* @return array<Type> Documents matching the JSON containment query
|
||||
*/
|
||||
public static function findByContains(string $tableName, array|object $criteria, string $className): array
|
||||
{
|
||||
return Document::mapResults(Document::queryByContains($tableName, $criteria), $className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the first matching document via JSON containment `@>`
|
||||
*
|
||||
* @param string $tableName The table from which documents should be retrieved
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
* @param class-string<Type> $className The type of document to be retrieved
|
||||
* @return Type|null The document, or null if none match
|
||||
*/
|
||||
public static function findFirstByContains(string $tableName, array|object $criteria, string $className): mixed
|
||||
{
|
||||
$query = Document::queryByContains($tableName, $criteria);
|
||||
$result = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
return $result ? Document::mapFromJson($result, $className) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents in a table via JSON Path match `@?`
|
||||
*
|
||||
* @param string $tableName The table from which documents should be retrieved
|
||||
* @param string $jsonPath The JSON Path to be matched
|
||||
* @return \PDOStatement An executed query ready to be fetched
|
||||
*/
|
||||
private static function queryByJsonPath(string $tableName, string $jsonPath): \PDOStatement
|
||||
{
|
||||
$query = pdo()->prepare(Query::findByJsonPath($tableName));
|
||||
$query->bindParam('@path', $jsonPath);
|
||||
$query->execute();
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents in a table via JSON Path match `@?`
|
||||
*
|
||||
* @param string $tableName The table from which documents should be retrieved
|
||||
* @param string $jsonPath The JSON Path to be matched
|
||||
* @param class-string<Type> $className The type of document to be retrieved
|
||||
* @return array<Type> Documents matching the JSON Path
|
||||
*/
|
||||
public static function findByJsonPath(string $tableName, string $jsonPath, string $className): array
|
||||
{
|
||||
return Document::mapResults(Document::queryByJsonPath($tableName, $jsonPath), $className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the first matching document via JSON Path match `@?`
|
||||
*
|
||||
* @param string $tableName The table from which documents should be retrieved
|
||||
* @param string $jsonPath The JSON Path to be matched
|
||||
* @param class-string<Type> $className The type of document to be retrieved
|
||||
* @return Type|null The document, or null if none match
|
||||
*/
|
||||
public static function findFirstByJsonPath(string $tableName, string $jsonPath, string $className): mixed
|
||||
{
|
||||
$query = Document::queryByJsonPath($tableName, $jsonPath);
|
||||
$result = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
return $result ? Document::mapFromJson($result, $className) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a full document
|
||||
*
|
||||
* @param string $tableName The table in which the document should be updated
|
||||
* @param string $docId The ID of the document to be updated
|
||||
* @param array|object $document The document to be updated
|
||||
*/
|
||||
public static function updateFull(string $tableName, string $docId, array|object $document)
|
||||
{
|
||||
Document::executeNonQuery(Query::updateFull($tableName), $docId, $document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a partial document by its ID
|
||||
*
|
||||
* @param string $tableName The table in which the document should be updated
|
||||
* @param string $docId The ID of the document to be updated
|
||||
* @param array|object $document The partial document to be updated
|
||||
*/
|
||||
public static function updatePartialById(string $tableName, string $docId, array|object $document)
|
||||
{
|
||||
Document::executeNonQuery(Query::updatePartialById($tableName), $docId, $document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update partial documents by JSON containment `@>`
|
||||
*
|
||||
* @param string $tableName The table in which documents should be updated
|
||||
* @param array|object $criteria The JSON containment criteria
|
||||
* @param array|object $document The document to be updated
|
||||
*/
|
||||
public static function updatePartialByContains(string $tableName, array|object $criteria, array|object $document)
|
||||
{
|
||||
$query = pdo()->prepare(Query::updatePartialByContains($tableName));
|
||||
$query->bindParam('@data', Query::jsonbDocParam($document));
|
||||
$query->bindParam('@criteria', Query::jsonbDocParam($criteria));
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update partial documents by JSON Path match `@?`
|
||||
*
|
||||
* @param string $tableName The table in which documents should be updated
|
||||
* @param string $jsonPath The JSON Path to be matched
|
||||
* @param array|object $document The document to be updated
|
||||
*/
|
||||
public static function updatePartialByJsonPath(string $tableName, string $jsonPath, array|object $document)
|
||||
{
|
||||
$query = pdo()->prepare(Query::updatePartialByContains($tableName));
|
||||
$query->bindParam('@data', Query::jsonbDocParam($document));
|
||||
$query->bindParam('@path', $jsonPath);
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a document by its ID
|
||||
*
|
||||
* @param string $tableName The table from which a document should be deleted
|
||||
* @param string $docId The ID of the document to be deleted
|
||||
*/
|
||||
public static function deleteById(string $tableName, string $docId)
|
||||
{
|
||||
Document::executeNonQuery(Query::deleteById($tableName), $docId, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete documents by JSON containment `@>`
|
||||
*
|
||||
* @param string $tableName The table from which documents should be deleted
|
||||
* @param array|object $criteria The criteria for the JSON containment query
|
||||
*/
|
||||
public static function deleteByContains(string $tableName, array|object $criteria)
|
||||
{
|
||||
$query = pdo()->prepare(Query::deleteByContains($tableName));
|
||||
$query->bindParam('@criteria', Query::jsonbDocParam($criteria));
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete documents by JSON Path match `@?`
|
||||
*
|
||||
* @param string $tableName The table from which documents should be deleted
|
||||
* @param string $jsonPath The JSON Path expression to be matched
|
||||
*/
|
||||
public static function deleteByJsonPath(string $tableName, string $jsonPath)
|
||||
{
|
||||
$query = pdo()->prepare(Query::deleteByJsonPath($tableName));
|
||||
$query->bindParam('@path', $jsonPath);
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
// TODO: custom
|
||||
|
||||
/**
|
||||
* Create and execute a custom query
|
||||
*
|
||||
* @param string $sql The SQL query to execute
|
||||
* @param array $params An associative array of parameters for the SQL query
|
||||
* @return PDOStatement The query, executed and ready to be fetched
|
||||
*/
|
||||
private static function createCustomQuery(string $sql, array $params): PDOStatement
|
||||
{
|
||||
$query = pdo()->prepare($sql);
|
||||
foreach ($params as $name => $value) {
|
||||
$query->bindParam($name, $value);
|
||||
}
|
||||
$query->execute();
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve documents via a custom query and mapping
|
||||
*
|
||||
* @param string $sql The SQL query to execute
|
||||
* @param array $params An associative array of parameters for the SQL query
|
||||
* @param callable $mapFunc A function that expects an associative array and returns a value of the desired type
|
||||
* @param class-string<Type> $className The type of document to be mapped
|
||||
* @return array<Type> The documents matching the query
|
||||
*/
|
||||
public static function customList(string $sql, array $params, string $className, callable $mapFunc): array
|
||||
{
|
||||
return array_map(
|
||||
fn ($it) => $mapFunc($it, $className),
|
||||
Document::createCustomQuery($sql, $params)->fetchAll(\PDO::FETCH_ASSOC));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a document via a custom query and mapping
|
||||
*
|
||||
* @param string $sql The SQL query to execute
|
||||
* @param array $params An associative array of parameters for the SQL query
|
||||
* @param callable $mapFunc A function that expects an associative array and returns a value of the desired type
|
||||
* @param class-string<Type> $className The type of document to be mapped
|
||||
* @return ?Type The document matching the query, or null if none is found
|
||||
*/
|
||||
public static function customSingle(string $sql, array $params, string $className, callable $mapFunc): mixed
|
||||
{
|
||||
$result = Document::createCustomQuery($sql, $params)->fetch(\PDO::FETCH_ASSOC);
|
||||
return $result ? $mapFunc($result, $className) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a custom query that does not return a result
|
||||
*
|
||||
* @param string $sql The SQL query to execute
|
||||
* @param array $params An associative array of parameters for the SQL query
|
||||
*/
|
||||
public static function customNonQuery(string $sql, array $params)
|
||||
{
|
||||
Document::createCustomQuery($sql, $params);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user