Add repo hygiene rules and ignore secrets
This commit is contained in:
76
server/src/Config/ConfigLoader.php
Normal file
76
server/src/Config/ConfigLoader.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Config;
|
||||
|
||||
final class ConfigLoader
|
||||
{
|
||||
private ConfigValidator $validator;
|
||||
private string $baseDir;
|
||||
|
||||
/** @var array<string,array<string,mixed>> */
|
||||
private array $cache = [];
|
||||
|
||||
public function __construct(ConfigValidator $validator, ?string $baseDir = null)
|
||||
{
|
||||
$this->validator = $validator;
|
||||
$this->baseDir = $baseDir ?? dirname(__DIR__, 3) . '/config';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function planetClasses(): array
|
||||
{
|
||||
return $this->load('planet_classes.json', function (array $config): void {
|
||||
$this->validator->validatePlanetClasses($config, 'planet_classes.json');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function races(): array
|
||||
{
|
||||
return $this->load('races.json', function (array $config): void {
|
||||
$this->validator->validateRaces($config, 'races.json');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function blueprintsBuildings(): array
|
||||
{
|
||||
return $this->load('blueprints_buildings.json', function (array $config): void {
|
||||
$this->validator->validateBlueprintsBuildings($config, 'blueprints_buildings.json');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(array<string,mixed>):void $validate
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function load(string $file, callable $validate): array
|
||||
{
|
||||
if (isset($this->cache[$file])) {
|
||||
return $this->cache[$file];
|
||||
}
|
||||
$path = rtrim($this->baseDir, '/') . '/' . $file;
|
||||
if (!file_exists($path)) {
|
||||
throw new \RuntimeException("Config-Datei nicht gefunden: {$path}");
|
||||
}
|
||||
$raw = file_get_contents($path);
|
||||
if ($raw === false) {
|
||||
throw new \RuntimeException("Config-Datei konnte nicht gelesen werden: {$path}");
|
||||
}
|
||||
$data = json_decode($raw, true, flags: JSON_THROW_ON_ERROR);
|
||||
if (!is_array($data)) {
|
||||
throw new \RuntimeException("Config-Datei muss ein JSON-Objekt sein: {$path}");
|
||||
}
|
||||
$validate($data);
|
||||
$this->cache[$file] = $data;
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
29
server/src/Config/ConfigValidationException.php
Normal file
29
server/src/Config/ConfigValidationException.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Config;
|
||||
|
||||
final class ConfigValidationException extends \RuntimeException
|
||||
{
|
||||
/** @var string[] */
|
||||
private array $errors;
|
||||
|
||||
/**
|
||||
* @param string[] $errors
|
||||
*/
|
||||
public function __construct(string $file, array $errors)
|
||||
{
|
||||
$this->errors = $errors;
|
||||
$message = "Config-Validation fehlgeschlagen: {$file}\n- " . implode("\n- ", $errors);
|
||||
parent::__construct($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
197
server/src/Config/ConfigValidator.php
Normal file
197
server/src/Config/ConfigValidator.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Config;
|
||||
|
||||
final class ConfigValidator
|
||||
{
|
||||
/**
|
||||
* @param array<string,mixed> $config
|
||||
*/
|
||||
public function validatePlanetClasses(array $config, string $file): void
|
||||
{
|
||||
$errors = [];
|
||||
foreach (['resources', 'weights', 'tiers', 'classes', 'global_bounds'] as $key) {
|
||||
if (!array_key_exists($key, $config)) {
|
||||
$errors[] = "Fehlender Key '{$key}'";
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['resources']) && (!is_array($config['resources']) || $config['resources'] === [])) {
|
||||
$errors[] = "'resources' muss ein nicht-leeres Array sein";
|
||||
}
|
||||
|
||||
$expected = $this->expectedResources();
|
||||
if (isset($config['resources']) && is_array($config['resources'])) {
|
||||
$missing = array_diff($expected, $config['resources']);
|
||||
if ($missing) {
|
||||
$errors[] = "'resources' fehlt: " . implode(', ', $missing);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['weights']) && !is_array($config['weights'])) {
|
||||
$errors[] = "'weights' muss ein Objekt sein";
|
||||
}
|
||||
|
||||
if (isset($config['weights']) && is_array($config['weights'])) {
|
||||
foreach ($expected as $res) {
|
||||
if (!isset($config['weights'][$res]) || !is_numeric($config['weights'][$res])) {
|
||||
$errors[] = "'weights.{$res}' muss numerisch sein";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['global_bounds']) && is_array($config['global_bounds'])) {
|
||||
if (!isset($config['global_bounds']['min']) || !is_numeric($config['global_bounds']['min'])) {
|
||||
$errors[] = "'global_bounds.min' muss numerisch sein";
|
||||
}
|
||||
if (!isset($config['global_bounds']['max']) || !is_numeric($config['global_bounds']['max'])) {
|
||||
$errors[] = "'global_bounds.max' muss numerisch sein";
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['tiers']) && is_array($config['tiers'])) {
|
||||
foreach ($config['tiers'] as $tierKey => $tier) {
|
||||
if (!is_array($tier)) {
|
||||
$errors[] = "Tier '{$tierKey}' muss ein Objekt sein";
|
||||
continue;
|
||||
}
|
||||
if (!isset($tier['target_score']) || !is_numeric($tier['target_score'])) {
|
||||
$errors[] = "Tier '{$tierKey}': 'target_score' fehlt oder ist nicht numerisch";
|
||||
}
|
||||
if (!isset($tier['epsilon']) || !is_numeric($tier['epsilon'])) {
|
||||
$errors[] = "Tier '{$tierKey}': 'epsilon' fehlt oder ist nicht numerisch";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['classes']) && is_array($config['classes'])) {
|
||||
foreach ($config['classes'] as $classKey => $class) {
|
||||
if (!is_array($class)) {
|
||||
$errors[] = "Klasse '{$classKey}' muss ein Objekt sein";
|
||||
continue;
|
||||
}
|
||||
if (!isset($class['spawn_weight']) || !is_numeric($class['spawn_weight'])) {
|
||||
$errors[] = "Klasse '{$classKey}': 'spawn_weight' fehlt oder ist nicht numerisch";
|
||||
}
|
||||
if (isset($class['constraints']) && !is_array($class['constraints'])) {
|
||||
$errors[] = "Klasse '{$classKey}': 'constraints' muss ein Objekt sein";
|
||||
}
|
||||
if (isset($class['constraints']) && is_array($class['constraints'])) {
|
||||
foreach ($class['constraints'] as $res => $constraint) {
|
||||
if (!is_array($constraint)) {
|
||||
$errors[] = "Constraint '{$classKey}.{$res}' muss ein Objekt sein";
|
||||
continue;
|
||||
}
|
||||
if (isset($constraint['min']) && !is_numeric($constraint['min'])) {
|
||||
$errors[] = "Constraint '{$classKey}.{$res}.min' muss numerisch sein";
|
||||
}
|
||||
if (isset($constraint['max']) && !is_numeric($constraint['max'])) {
|
||||
$errors[] = "Constraint '{$classKey}.{$res}.max' muss numerisch sein";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($class['temperature_range_c'])) {
|
||||
$range = $class['temperature_range_c'];
|
||||
if (!is_array($range) || count($range) !== 2) {
|
||||
$errors[] = "Klasse '{$classKey}': 'temperature_range_c' muss ein Array [min,max] sein";
|
||||
} else {
|
||||
[$min, $max] = array_values($range);
|
||||
if (!is_numeric($min) || !is_numeric($max)) {
|
||||
$errors[] = "Klasse '{$classKey}': 'temperature_range_c' Werte müssen numerisch sein";
|
||||
} elseif ((float)$min > (float)$max) {
|
||||
$errors[] = "Klasse '{$classKey}': 'temperature_range_c' min darf nicht größer als max sein";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
throw new ConfigValidationException($file, $errors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $config
|
||||
*/
|
||||
public function validateRaces(array $config, string $file): void
|
||||
{
|
||||
$errors = [];
|
||||
if (!isset($config['races']) || !is_array($config['races'])) {
|
||||
$errors[] = "'races' muss ein Objekt sein";
|
||||
}
|
||||
if (isset($config['races']) && is_array($config['races'])) {
|
||||
foreach ($config['races'] as $raceKey => $race) {
|
||||
if (!is_array($race)) {
|
||||
$errors[] = "Race '{$raceKey}' muss ein Objekt sein";
|
||||
continue;
|
||||
}
|
||||
if (empty($race['name'])) {
|
||||
$errors[] = "Race '{$raceKey}': 'name' fehlt";
|
||||
}
|
||||
if (isset($race['modifiers']) && !is_array($race['modifiers'])) {
|
||||
$errors[] = "Race '{$raceKey}': 'modifiers' muss ein Objekt sein";
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($errors) {
|
||||
throw new ConfigValidationException($file, $errors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $config
|
||||
*/
|
||||
public function validateBlueprintsBuildings(array $config, string $file): void
|
||||
{
|
||||
$errors = [];
|
||||
if (!isset($config['blueprints']) || !is_array($config['blueprints'])) {
|
||||
$errors[] = "'blueprints' muss ein Array sein";
|
||||
}
|
||||
if (isset($config['blueprints']) && is_array($config['blueprints'])) {
|
||||
foreach ($config['blueprints'] as $idx => $bp) {
|
||||
if (!is_array($bp)) {
|
||||
$errors[] = "Blueprint #{$idx} muss ein Objekt sein";
|
||||
continue;
|
||||
}
|
||||
foreach (['kind', 'key', 'name'] as $req) {
|
||||
if (empty($bp[$req])) {
|
||||
$errors[] = "Blueprint #{$idx}: '{$req}' fehlt";
|
||||
}
|
||||
}
|
||||
if (isset($bp['effects']) && !is_array($bp['effects'])) {
|
||||
$errors[] = "Blueprint '{$bp['key'] ?? $idx}': 'effects' muss ein Array sein";
|
||||
}
|
||||
if (isset($bp['requirements']) && !is_array($bp['requirements'])) {
|
||||
$errors[] = "Blueprint '{$bp['key'] ?? $idx}': 'requirements' muss ein Array sein";
|
||||
}
|
||||
if (isset($bp['access']) && !is_array($bp['access'])) {
|
||||
$errors[] = "Blueprint '{$bp['key'] ?? $idx}': 'access' muss ein Objekt sein";
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($errors) {
|
||||
throw new ConfigValidationException($file, $errors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function expectedResources(): array
|
||||
{
|
||||
return [
|
||||
'metal',
|
||||
'alloy',
|
||||
'crystals',
|
||||
'energy',
|
||||
'credits',
|
||||
'population',
|
||||
'water',
|
||||
'deuterium',
|
||||
'food',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user