|
<?php |
|
|
|
namespace FreePBX; |
|
|
|
use PDO; |
|
use Generator; |
|
|
|
/** |
|
* A memory-efficient, read-only helper for the FreePBX key-value store. |
|
* |
|
* This class uses PHP generators to stream data directly from the database, |
|
* avoiding loading large result sets into memory. It is intended as a |
|
* high-performance alternative to the fetching methods in DB_Helper. |
|
*/ |
|
class StreamingKvReader |
|
{ |
|
private PDO $pdo; |
|
private string $tableName; |
|
|
|
/** |
|
* @param PDO $pdo The database connection object. |
|
* @param string $moduleClass The fully qualified class name of the module (e.g., MyModule\Main::class). This is used to derive the table name. |
|
*/ |
|
public function __construct(PDO $pdo, string $moduleClass) |
|
{ |
|
$this->pdo = $pdo; |
|
$this->tableName = $this->deriveTableName($moduleClass); |
|
} |
|
|
|
/** |
|
* Derives the kvstore table name from a module's class name. |
|
* |
|
* @param string $className The FQCN of the module. |
|
* @return string The derived table name, e.g., 'kvstore_MyModule_Main'. |
|
*/ |
|
private function deriveTableName(string $className): string |
|
{ |
|
// This mirrors the logic from the original DB_Helper. |
|
return "kvstore_" . str_replace('\\', '_', $className); |
|
} |
|
|
|
/** |
|
* getConfigGenerator: Fetches a single value. |
|
* |
|
* Note: A generator is not beneficial here as we only expect one result. |
|
* This method performs a direct fetch. |
|
* |
|
* @param string $key The key to retrieve. |
|
* @param string $id The optional sub-group ID. |
|
* @return mixed The unserialized value, or null if not found. |
|
*/ |
|
public function getConfigValue(string $key, string $id = 'noid') |
|
{ |
|
$sql = sprintf( |
|
"SELECT `val`, `type` FROM `%s` WHERE `key` = :key AND `id` = :id", |
|
$this->tableName |
|
); |
|
$stmt = $this->pdo->prepare($sql); |
|
$stmt->execute([':key' => $key, ':id' => $id]); |
|
$res = $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
|
return $res ? $this->hydrateValue($res['val'], $res['type']) : null; |
|
} |
|
|
|
/** |
|
* getAllGenerator: Streams all key-value pairs for a given ID. |
|
* |
|
* This is the primary generator method. It fetches all rows for an ID |
|
* and yields them one by one, keeping memory usage minimal. |
|
* |
|
* @param string $id The sub-group ID. |
|
* @return Generator Yields an associative array ['key' =>, 'value' =>] for each row. |
|
*/ |
|
public function getAllGenerator(string $id = 'noid'): Generator |
|
{ |
|
// A single query to get all data for the ID. |
|
$sql = sprintf( |
|
"SELECT `key`, `val`, `type` FROM `%s` WHERE `id` = :id ORDER BY `key`", |
|
$this->tableName |
|
); |
|
$stmt = $this->pdo->prepare($sql); |
|
$stmt->execute([':id' => $id]); |
|
|
|
// Fetch and yield one row at a time. The full result set never exists in PHP memory. |
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { |
|
yield [ |
|
'key' => $row['key'], |
|
'value' => $this->hydrateValue($row['val'], $row['type']), |
|
]; |
|
} |
|
} |
|
|
|
/** |
|
* getAllKeysGenerator: Streams all keys for a given ID. |
|
* |
|
* @param string $id The sub-group ID. |
|
* @return Generator Yields a string key for each row. |
|
*/ |
|
public function getAllKeysGenerator(string $id = 'noid'): Generator |
|
{ |
|
$sql = sprintf( |
|
"SELECT `key` FROM `%s` WHERE `id` = :id ORDER BY `key`", |
|
$this->tableName |
|
); |
|
$stmt = $this->pdo->prepare($sql); |
|
$stmt->execute([':id' => $id]); |
|
|
|
while ($key = $stmt->fetch(PDO::FETCH_COLUMN)) { |
|
yield $key; |
|
} |
|
} |
|
|
|
/** |
|
* getAllIdsGenerator: Streams all distinct IDs in the table. |
|
* |
|
* @return Generator Yields a string ID for each distinct ID found. |
|
*/ |
|
public function getAllIdsGenerator(): Generator |
|
{ |
|
$sql = sprintf( |
|
"SELECT DISTINCT(`id`) FROM `%s` WHERE `id` <> 'noid'", |
|
$this->tableName |
|
); |
|
$stmt = $this->pdo->prepare($sql); |
|
$stmt->execute(); |
|
while ($id = $stmt->fetch(PDO::FETCH_COLUMN)) { |
|
yield $id; |
|
} |
|
} |
|
|
|
/** |
|
* Hydrates a raw database value based on its type hint. |
|
* |
|
* @param string $value The raw value from the database. |
|
* @param ?string $type The type hint (e.g., 'json-arr', 'json-obj'). |
|
* @return mixed The processed value. |
|
*/ |
|
private function hydrateValue(string $value, ?string $type) |
|
{ |
|
// This mirrors the type-handling logic from the original getConfig. |
|
// Note: The original 'blob' logic is complex and likely needs a separate getBlob call. |
|
// For this streaming reader, we assume blobs are handled elsewhere or are not the primary use case. |
|
return match ($type) { |
|
'json-arr' => json_decode($value, true), |
|
'json-obj' => json_decode($value), |
|
default => $value, |
|
}; |
|
} |
|
} |