syokuhou/src/DbConfig.php
2023-10-20 21:21:17 +00:00

227 lines
6.7 KiB
PHP

<?php
// DbConfig.php
// Created: 2023-10-20
// Updated: 2023-10-20
namespace Syokuhou;
use InvalidArgumentException;
use Index\Data\DbStatementCache;
use Index\Data\DbTools;
use Index\Data\IDbConnection;
/**
* Provides a configuration based on a {@see IDbConnection} instance.
*
* @todo provide table name in constructor
* @todo scan for vendor specific queries and generalise them
* @todo getValues() parsing should probably be done external so it can be reused
*/
class DbConfig implements IConfig {
use MutableConfigTrait, GetValueInfoTrait, GetValuesTrait;
private DbStatementCache $cache;
/** @var array<string, DbConfigValueInfo> */
private array $values = [];
public function __construct(
IDbConnection $dbConn,
private string $tableName,
private string $nameField = 'config_name',
private string $valueField = 'config_value'
) {
$this->cache = new DbStatementCache($dbConn);
}
public static function validateName(string $name): bool {
// this should better validate the format, this allows for a lot of shittery
return preg_match('#^([a-z][a-zA-Z0-9._]+)$#', $name) === 1;
}
/**
* Resets value cache.
*/
public function reset(): void {
$this->values = [];
}
/**
* Unloads specifics items from the local cache.
*
* @param string|string[] $names Names of values to unload.
*/
public function unload(string|array $names): void {
if(empty($names))
return;
if(is_string($names))
$names = [$names];
foreach($names as $name)
unset($this->values[$name]);
}
public function getSeparator(): string {
return '.';
}
public function scopeTo(string ...$prefix): IConfig {
return new ScopedConfig($this, $prefix);
}
public function hasValues(string|array $names): bool {
if(empty($names))
return true;
if(is_string($names))
$names = [$names];
$cachedNames = array_keys($this->values);
$names = array_diff($names, $cachedNames);
if(!empty($names)) {
// array_diff preserves keys, the for() later would fuck up without it
$names = array_values($names);
$nameCount = count($names);
$stmt = $this->cache->get(sprintf(
'SELECT COUNT(*) FROM %s WHERE %s IN (%s)',
$this->tableName, $this->nameField,
DbTools::prepareListString($nameCount)
));
for($i = 0; $i < $nameCount; ++$i)
$stmt->addParameter($i + 1, $names[$i]);
$stmt->execute();
$result = $stmt->getResult();
if($result->next())
return $result->getInteger(0) >= $nameCount;
}
return true;
}
public function removeValues(string|array $names): void {
if(empty($names))
return;
if(is_string($names))
$names = [$names];
foreach($names as $name)
unset($this->values[$name]);
$nameCount = count($names);
$stmt = $this->cache->get(sprintf(
'DELETE FROM %s WHERE %s IN (%s)',
$this->tableName, $this->nameField,
DbTools::prepareListString($nameCount)
));
for($i = 0; $i < $nameCount; ++$i)
$stmt->addParameter($i + 1, $names[$i]);
$stmt->execute();
}
public function getAllValueInfos(int $range = 0, int $offset = 0): array {
$this->reset();
$infos = [];
$hasRange = $range !== 0;
$query = sprintf('SELECT %s, %s FROM %s', $this->nameField, $this->valueField, $this->tableName);
if($hasRange) {
if($range < 0)
throw new InvalidArgumentException('$range must be a positive integer.');
if($offset < 0)
throw new InvalidArgumentException('$offset must be greater than zero if a range is specified.');
$query .= ' LIMIT ? OFFSET ?';
}
$stmt = $this->cache->get($query);
if($hasRange) {
$stmt->addParameter(1, $range);
$stmt->addParameter(2, $offset);
}
$stmt->execute();
$result = $stmt->getResult();
while($result->next()) {
$name = $result->getString(0);
$infos[] = $this->values[$name] = new DbConfigValueInfo($result);
}
return $infos;
}
public function getValueInfos(string|array $names): array {
if(empty($names))
return [];
if(is_string($names))
$names = [$names];
$infos = [];
$skip = [];
foreach($names as $name)
if(array_key_exists($name, $this->values)) {
$infos[] = $this->values[$name];
$skip[] = $name;
}
$names = array_diff($names, $skip);
if(!empty($names)) {
// array_diff preserves keys, the for() later would fuck up without it
$names = array_values($names);
$nameCount = count($names);
$stmt = $this->cache->get(sprintf(
'SELECT %s, %s FROM %s WHERE config_name IN (%s)',
$this->nameField, $this->valueField, $this->tableName,
DbTools::prepareListString($nameCount)
));
for($i = 0; $i < $nameCount; ++$i)
$stmt->addParameter($i + 1, $names[$i]);
$stmt->execute();
$result = $stmt->getResult();
while($result->next()) {
$name = $result->getString(0);
$infos[] = $this->values[$name] = new DbConfigValueInfo($result);
}
}
return $infos;
}
public function setValues(array $values): void {
if(empty($values))
return;
$stmt = $this->cache->get(sprintf(
'INSERT INTO %s (%s, %s) VALUES (?, ?)',
$this->tableName, $this->nameField, $this->valueField
));
foreach($values as $name => $value) {
if(!self::validateName($name))
throw new InvalidArgumentException('Invalid name encountered in $values.');
if(is_array($value)) {
foreach($value as $entry)
if(!is_scalar($entry))
throw new InvalidArgumentException('An array value in $values contains a non-scalar type.');
} elseif(!is_scalar($value))
throw new InvalidArgumentException('Invalid value type encountered in $values.');
$this->removeValues($name);
$stmt->addParameter(1, $name);
$stmt->addParameter(2, serialize($value));
$stmt->execute();
}
}
}