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