feat: add app and database modules
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ============================================================
|
||||
*
|
||||
* @project biiproject
|
||||
*
|
||||
* @author Andika Debi Putra
|
||||
*
|
||||
* @email andikadebiputra@gmail.com
|
||||
*
|
||||
* @website https://biiproject.com
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Andika Debi Putra
|
||||
* @license Proprietary - All Rights Reserved
|
||||
*
|
||||
* @version 1.0.0
|
||||
*
|
||||
* @created 2026-05-01
|
||||
* ============================================================
|
||||
*
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file is strictly prohibited without prior written
|
||||
* permission from the author.
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
namespace App\Services\SystemConfig;
|
||||
|
||||
use App\Models\SystemSettingRevision;
|
||||
use App\Repositories\SystemSettingRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class SystemConfigService
|
||||
{
|
||||
private const CACHE_KEY = 'system_settings.all';
|
||||
|
||||
private const PUBLIC_CACHE_KEY = 'system_settings.public';
|
||||
|
||||
private const CACHE_TTL_MINUTES = 60;
|
||||
|
||||
protected static ?array $resolvedSettings = null;
|
||||
|
||||
public function __construct(
|
||||
protected SystemSettingRepository $repository,
|
||||
protected SettingFileUploader $fileUploader = new SettingFileUploader,
|
||||
) {}
|
||||
|
||||
public static function definitions(): array
|
||||
{
|
||||
return SettingDefinitions::ALL;
|
||||
}
|
||||
|
||||
public function all(bool $forceRefresh = false): array
|
||||
{
|
||||
if ($forceRefresh) {
|
||||
$this->invalidateCache();
|
||||
}
|
||||
|
||||
if (self::$resolvedSettings !== null) {
|
||||
return self::$resolvedSettings;
|
||||
}
|
||||
|
||||
try {
|
||||
return self::$resolvedSettings = Cache::remember(
|
||||
self::CACHE_KEY,
|
||||
now()->addMinutes(self::CACHE_TTL_MINUTES),
|
||||
fn () => $this->resolveAll(),
|
||||
);
|
||||
} catch (\Exception) {
|
||||
return self::$resolvedSettings = $this->resolveAll();
|
||||
}
|
||||
}
|
||||
|
||||
public function getPublicSettings(): array
|
||||
{
|
||||
try {
|
||||
return Cache::remember(
|
||||
self::PUBLIC_CACHE_KEY,
|
||||
now()->addMinutes(self::CACHE_TTL_MINUTES),
|
||||
fn () => $this->buildPublicSettings(),
|
||||
);
|
||||
} catch (\Exception) {
|
||||
return $this->buildPublicSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public function grouped(): array
|
||||
{
|
||||
$all = $this->all();
|
||||
$grouped = [];
|
||||
|
||||
foreach (SettingDefinitions::ALL as $key => $meta) {
|
||||
$grouped[$meta['group']][$key] = $all[$key] ?? $meta['default'] ?? null;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
if (! array_key_exists($key, SettingDefinitions::ALL)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $this->all()[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function update(array $input, array $files = [], ?int $actorId = null, ?Request $request = null): void
|
||||
{
|
||||
$current = $this->all();
|
||||
|
||||
foreach (SettingDefinitions::ALL as $key => $meta) {
|
||||
$oldValue = $current[$key] ?? $meta['default'] ?? null;
|
||||
$newValue = $this->resolveNewValue($key, $meta, $input, $files, $oldValue);
|
||||
|
||||
if (SettingValueCaster::isUnchanged($oldValue, $newValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$setting = $this->repository->upsert([
|
||||
'key' => $key,
|
||||
'value' => SettingValueCaster::serialize($newValue),
|
||||
'type' => $meta['type'],
|
||||
'group' => $meta['group'],
|
||||
'is_public' => (bool) $meta['is_public'],
|
||||
'description' => $meta['description'],
|
||||
'created_by' => $actorId,
|
||||
'updated_by' => $actorId,
|
||||
]);
|
||||
|
||||
$this->writeRevision($setting->id, $key, $oldValue, $newValue, $actorId, $request);
|
||||
}
|
||||
|
||||
$this->invalidateCache();
|
||||
}
|
||||
|
||||
protected function resolveNewValue(string $key, array $meta, array $input, array $files, mixed $oldValue): mixed
|
||||
{
|
||||
if ($meta['type'] === 'image_path') {
|
||||
return isset($files[$key])
|
||||
? $this->fileUploader->replace($key, $files[$key], $oldValue)
|
||||
: $oldValue;
|
||||
}
|
||||
|
||||
return array_key_exists($key, $input)
|
||||
? SettingValueCaster::normalize($input[$key], (string) $meta['type'])
|
||||
: $oldValue;
|
||||
}
|
||||
|
||||
protected function resolveAll(): array
|
||||
{
|
||||
try {
|
||||
if (! $this->repository->tableExists()) {
|
||||
return $this->definitionDefaults();
|
||||
}
|
||||
$rows = $this->repository->all();
|
||||
} catch (\Exception) {
|
||||
return $this->definitionDefaults();
|
||||
}
|
||||
|
||||
$resolved = [];
|
||||
foreach (SettingDefinitions::ALL as $key => $meta) {
|
||||
$record = $rows->firstWhere('key', $key);
|
||||
$resolved[$key] = $record
|
||||
? SettingValueCaster::deserialize($record->value, (string) $record->type)
|
||||
: ($meta['default'] ?? null);
|
||||
}
|
||||
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
protected function buildPublicSettings(): array
|
||||
{
|
||||
$all = $this->all();
|
||||
$public = [];
|
||||
|
||||
foreach (SettingDefinitions::ALL as $key => $meta) {
|
||||
if (! $meta['is_public']) {
|
||||
continue;
|
||||
}
|
||||
$public[$key] = $all[$key] ?? $meta['default'] ?? null;
|
||||
}
|
||||
|
||||
return $public;
|
||||
}
|
||||
|
||||
protected function definitionDefaults(): array
|
||||
{
|
||||
$defaults = [];
|
||||
foreach (SettingDefinitions::ALL as $key => $meta) {
|
||||
$defaults[$key] = $meta['default'] ?? null;
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
protected function writeRevision(
|
||||
int $settingId,
|
||||
string $key,
|
||||
mixed $oldValue,
|
||||
mixed $newValue,
|
||||
?int $actorId,
|
||||
?Request $request,
|
||||
): void {
|
||||
SystemSettingRevision::query()->create([
|
||||
'system_setting_id' => $settingId,
|
||||
'key' => $key,
|
||||
'old_value' => $oldValue === null ? null : json_encode($oldValue, JSON_UNESCAPED_SLASHES),
|
||||
'new_value' => $newValue === null ? null : json_encode($newValue, JSON_UNESCAPED_SLASHES),
|
||||
'changed_by' => $actorId,
|
||||
'changed_ip' => $request?->ip(),
|
||||
'changed_agent' => $request?->userAgent(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function invalidateCache(): void
|
||||
{
|
||||
Cache::forget(self::CACHE_KEY);
|
||||
Cache::forget(self::PUBLIC_CACHE_KEY);
|
||||
self::$resolvedSettings = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user