misuzu/src/Config/DbConfig.php
2023-08-31 21:33:34 +00:00

318 lines
9.9 KiB
PHP

<?php
namespace Misuzu\Config;
use RuntimeException;
use InvalidArgumentException;
use Index\Data\DataException;
use Index\Data\DbStatementCache;
use Index\Data\DbTools;
use Index\Data\IDbConnection;
use Index\Data\IDbStatement;
use Index\Data\IDbResult;
use Misuzu\Pagination;
class DbConfig implements IConfig {
private DbStatementCache $cache;
private array $values = [];
public function __construct(IDbConnection $dbConn) {
$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;
}
public function reset(): void {
$this->values = [];
}
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 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 msz_config WHERE config_name IN (%s)',
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 msz_config WHERE config_name IN (%s)',
DbTools::prepareListString($nameCount)
));
for($i = 0; $i < $nameCount; ++$i)
$stmt->addParameter($i + 1, $names[$i]);
$stmt->execute();
}
public function getAllValueInfos(?Pagination $pagination = null): array {
$this->reset();
$infos = [];
$hasPagination = $pagination !== null;
$query = 'SELECT config_name, config_value FROM msz_config';
if($hasPagination)
$query .= ' LIMIT ? RANGE ?';
$stmt = $this->cache->get($query);
if($hasPagination) {
$stmt->addParameter(1, $pagination->getRange());
$stmt->addParameter(2, $pagination->getOffset());
}
$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 config_name, config_value FROM msz_config WHERE config_name IN (%s)',
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 getValueInfo(string $name): ?IConfigValueInfo {
$infos = $this->getValueInfos($name);
return empty($infos) ? null : $infos[0];
}
public function getValues(array $specs): array {
$names = [];
foreach($specs as $key => $spec) {
if(is_string($spec)) {
$name = $spec;
$default = null;
$alias = null;
} elseif(is_array($spec) && !empty($spec)) {
$name = $spec[0];
$default = $spec[1] ?? null;
$alias = $spec[2] ?? null;
} else
throw new InvalidArgumentException('$specs array contains an invalid entry.');
if(($colon = strpos($name, ':')) !== false) {
$type = substr($name, $colon + 1, 1);
$name = substr($name, 0, $colon);
} else $type = '';
$names[] = $name;
$specs[$key] = [
'name' => $name,
'type' => $type,
'default' => $default,
'alias' => $alias,
];
}
$infos = $this->getValueInfos($names);
$results = [];
foreach($specs as $spec) {
foreach($infos as $infoTest)
if($infoTest->getName() === $spec['name']) {
$info = $infoTest;
break;
}
$resultName = $spec['alias'] ?? $spec['name'];
if(!isset($info)) {
$defaultValue = $spec['default'] ?? null;
if($spec['type'] !== '')
settype($defaultValue, match($spec['type']) {
's' => 'string',
'a' => 'array',
'i' => 'int',
'b' => 'bool',
'f' => 'float',
'd' => 'double',
default => throw new RuntimeException('Invalid type letter encountered.'),
});
$results[$resultName] = $defaultValue;
continue;
}
$results[$resultName] = match($spec['type']) {
's' => $info->getString(),
'a' => $info->getArray(),
'i' => $info->getInteger(),
'b' => $info->getBoolean(),
'f' => $info->getFloat(),
'd' => $info->getFloat(),
'' => $info->getValue(),
default => throw new RuntimeException('Unknown type encountered in $specs.'),
};
unset($info);
}
return $results;
}
public function getString(string $name, string $default = ''): string {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isString() ? $valueInfo->getString() : $default;
}
public function getInteger(string $name, int $default = 0): int {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isInteger() ? $valueInfo->getInteger() : $default;
}
public function getFloat(string $name, float $default = 0): float {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isFloat() ? $valueInfo->getFloat() : $default;
}
public function getBoolean(string $name, bool $default = false): bool {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isBoolean() ? $valueInfo->getBoolean() : $default;
}
public function getArray(string $name, array $default = []): array {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isArray() ? $valueInfo->getArray() : $default;
}
public function setValues(array $values): void {
if(empty($values))
return;
$valueCount = count($values);
$stmt = $this->cache->get(sprintf(
'REPLACE INTO msz_config (config_name, config_value) VALUES %s',
DbTools::prepareListString($valueCount, '(?, ?)')
));
$args = 0;
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.');
$stmt->addParameter(++$args, $name);
$stmt->addParameter(++$args, serialize($value));
}
$stmt->execute();
}
public function setString(string $name, string $value): void {
$this->setValues([$name => $value]);
}
public function setInteger(string $name, int $value): void {
$this->setValues([$name => $value]);
}
public function setFloat(string $name, float $value): void {
$this->setValues([$name => $value]);
}
public function setBoolean(string $name, bool $value): void {
$this->setValues([$name => $value]);
}
public function setArray(string $name, array $value): void {
$this->setValues([$name => $value]);
}
}