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]); } }