update(...) or $storeDetails->save() directly — a * write bypassing this service leaves current()'s forever-cache stale. */ class StoreDetailsService { public const CACHE_KEY = 'store-details'; /** * Forever-cached — read on every storefront request that shows store * details (e.g. the checkout confirmation page's bank transfer * instructions), so this should never re-query the database on a normal * request. Only ever invalidated by update() below, via * FlushStoreDetailsCache reacting to StoreDetailsUpdated. */ public function current(): StoreDetails { return Cache::rememberForever( self::CACHE_KEY, fn () => $this->firstOrCreate(), ); } public function update(array $attributes): StoreDetails { $storeDetails = $this->firstOrCreate(); $storeDetails->update($attributes); Event::dispatch(new StoreDetailsUpdated($storeDetails)); return $storeDetails; } /** * A freshly-created row must never leave a translatable column * genuinely NULL — Lunar's own TranslatedText component (Modules\Core\ * Store\Filament\Pages\ManageStoreDetails's `name`/`address`/ * `bank_transfer_instructions` fields) silently drops every keystroke * on re-render when the field it's editing starts out NULL rather than * an empty per-locale array. Real-world precedent (PaymentMethod's own * translatable `name` column) never hits this, because every * PaymentMethod row is created THROUGH the same Filament form that * immediately fills `name` — this singleton is instead created blank * and opened for editing in the same visit, which is exactly the gap * that surfaces the bug. Caught and fixed after the fact, verified via * tinker: seeding a real (non-null) array made typing into the field * persist correctly, confirming NULL was the trigger. */ private function firstOrCreate(): StoreDetails { return StoreDetails::query()->firstOrCreate([], [ 'name' => $this->emptyPerLocale(), 'address' => $this->emptyPerLocale(), 'bank_transfer_instructions' => $this->emptyPerLocale(), ]); } private function emptyPerLocale(): array { return Language::query()->pluck('code')->mapWithKeys(fn (string $code) => [$code => ''])->all(); } }