127 lines
3.9 KiB
PHP
127 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Config as ConfigModel;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Str;
|
|
|
|
final class Config
|
|
{
|
|
/** Typed public property for the content group's disclaimer field. */
|
|
public string $contentDisclaimer = '';
|
|
|
|
/** Indexed config class instances keyed by their group key string. */
|
|
private array $configClasses = [];
|
|
|
|
/**
|
|
* Auto-discovers Config Field classes from app/Configs/, loads or creates
|
|
* their database rows, and populates typed public properties with cast values.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->discoverConfigClasses();
|
|
$this->hydrateProperties();
|
|
}
|
|
|
|
/**
|
|
* Scans app/Configs/ for PHP files, instantiates each class,
|
|
* and indexes them by their key property.
|
|
*/
|
|
private function discoverConfigClasses(): void
|
|
{
|
|
$files = glob(app_path('Configs/*.php')) ?: [];
|
|
|
|
foreach ($files as $file) {
|
|
$className = 'App\\Configs\\'.pathinfo($file, PATHINFO_FILENAME);
|
|
$instance = new $className;
|
|
$this->configClasses[$instance->key] = $instance;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Iterates over all discovered config classes, ensures a DB row exists,
|
|
* resolves typed field values, and assigns them to public properties.
|
|
*/
|
|
private function hydrateProperties(): void
|
|
{
|
|
try {
|
|
$dbRecords = ConfigModel::all()->keyBy('key');
|
|
|
|
foreach ($this->configClasses as $key => $instance) {
|
|
$record = $this->ensureDbRecord($key, $dbRecords);
|
|
$jsonValue = Arr::wrap((array) ($record->json_value ?? []));
|
|
$this->assignFieldProperties($key, $instance, $jsonValue);
|
|
}
|
|
} catch (QueryException) {
|
|
// Silently skip if configs table does not yet exist during migrations.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures a database record exists for the given config key,
|
|
* creating one with an empty json_value if it does not.
|
|
*/
|
|
private function ensureDbRecord(string $key, Collection $dbRecords): ConfigModel
|
|
{
|
|
if ($dbRecords->has($key)) {
|
|
return $dbRecords->get($key);
|
|
}
|
|
|
|
return ConfigModel::firstOrCreate(
|
|
['key' => $key],
|
|
['json_value' => ''],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resolves each field value from json_value (with default fallback),
|
|
* applies type casting, and assigns to the matching typed public property.
|
|
*/
|
|
private function assignFieldProperties(string $key, object $instance, array $jsonValue): void
|
|
{
|
|
foreach ($instance->getFieldKeys() as $fieldKey => $definition) {
|
|
$type = Arr::get($definition, 'type', 'string');
|
|
$rawValue = Arr::get($jsonValue, $fieldKey);
|
|
|
|
if ($rawValue === null || $rawValue === '') {
|
|
$rawValue = $instance->getDefault($fieldKey);
|
|
}
|
|
|
|
$castedValue = $this->castValue($rawValue, $type);
|
|
$propertyName = Str::camel($key).Str::studly($fieldKey);
|
|
|
|
if (property_exists($this, $propertyName)) {
|
|
$this->{$propertyName} = $castedValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Casts a raw value to the PHP type defined by the config field type string.
|
|
*/
|
|
private function castValue(mixed $value, string $type): mixed
|
|
{
|
|
return match ($type) {
|
|
'string', 'markdown' => (string) $value,
|
|
'int', 'integer' => (int) $value,
|
|
'bool', 'boolean' => (bool) $value,
|
|
'float' => (float) $value,
|
|
default => $value,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns the Config Field class instance for a given group key,
|
|
* or null when no class has been discovered for that key.
|
|
*/
|
|
public function getConfigClassForGroup(string $group): ?object
|
|
{
|
|
return Arr::get($this->configClasses, $group);
|
|
}
|
|
}
|