<?php
declare(strict_types=1);
namespace Druidfi\Mysqldump;
use Druidfi\Mysqldump\Attribute\Constraint;
use Druidfi\Mysqldump\Attribute\DefaultValue;
use Druidfi\Mysqldump\Attribute\Deprecated;
use Exception;
use ReflectionClass;
use ReflectionClassConstant;
class ConfigValidator
{
private static ?array $metadataCache = null;
private static function buildMetadata(): array
{
if (self::$metadataCache !== null) {
return self::$metadataCache;
}
$metadata = [];
$reflection = new ReflectionClass(ConfigOption::class);
foreach ($reflection->getReflectionConstants() as $constant) {
$optionName = $constant->getValue();
$metadata[$optionName] = self::extractConstantMetadata($constant);
}
self::$metadataCache = $metadata;
return $metadata;
}
private static function extractConstantMetadata(ReflectionClassConstant $constant): array
{
$data = [
'default' => null,
'description' => null,
'constraint' => null,
'deprecated' => null,
];
$defaultAttrs = $constant->getAttributes(DefaultValue::class);
if (!empty($defaultAttrs)) {
$attr = $defaultAttrs[0]->newInstance();
$data['default'] = $attr->value;
$data['description'] = $attr->description;
}
$constraintAttrs = $constant->getAttributes(Constraint::class);
if (!empty($constraintAttrs)) {
$data['constraint'] = $constraintAttrs[0]->newInstance();
}
$deprecatedAttrs = $constant->getAttributes(Deprecated::class);
if (!empty($deprecatedAttrs)) {
$data['deprecated'] = $deprecatedAttrs[0]->newInstance();
}
return $data;
}
public static function getDefaults(): array
{
$metadata = self::buildMetadata();
$defaults = [];
foreach ($metadata as $optionName => $data) {
if ($data['default'] !== null) {
$defaults[$optionName] = $data['default'];
}
}
return $defaults;
}
public static function validate(string $optionName, mixed $value): void
{
$metadata = self::buildMetadata();
if (!isset($metadata[$optionName])) {
return;
}
$constraint = $metadata[$optionName]['constraint'];
if ($constraint === null) {
return;
}
if ($constraint->allowedValues !== null) {
if (!in_array($value, $constraint->allowedValues, true)) {
$allowed = implode(', ', $constraint->allowedValues);
$message = $constraint->message ?? "Invalid value for '{$optionName}'. Allowed: {$allowed}";
throw new Exception($message);
}
}
if (is_numeric($value)) {
if ($constraint->min !== null && $value < $constraint->min) {
$message = $constraint->message ?? "Value for '{$optionName}' must be at least {$constraint->min}";
throw new Exception($message);
}
if ($constraint->max !== null && $value > $constraint->max) {
$message = $constraint->message ?? "Value for '{$optionName}' must be at most {$constraint->max}";
throw new Exception($message);
}
}
if ($constraint->pattern !== null && is_string($value)) {
if (!preg_match($constraint->pattern, $value)) {
$message = $constraint->message ?? "Value for '{$optionName}' does not match required pattern";
throw new Exception($message);
}
}
}
public static function checkDeprecated(string $optionName): ?array
{
$metadata = self::buildMetadata();
if (!isset($metadata[$optionName])) {
return null;
}
$deprecated = $metadata[$optionName]['deprecated'];
if ($deprecated === null) {
return null;
}
return [
'deprecated' => true,
'reason' => $deprecated->reason,
'alternative' => $deprecated->alternative,
'since' => $deprecated->since,
];
}
public static function validateAll(array $settings): void
{
foreach ($settings as $optionName => $value) {
self::validate($optionName, $value);
}
}
}