一張 settings 表搞定所有後台設定,不要每加一個設定就改 schema。
電商系統的設定會一直長:運費多少、免運門檻、哪幾家超商開啟、點數怎麼回饋、折抵上限幾 %、發票要不要自動開⋯⋯
每加一個就開一個欄位,你的 migration 會變成一部編年史。每加一個就開一張表,你會有十張只有一列資料的表。
我用一張表:
settings
key string unique
value string
type string
用法:
Setting::get('points.max_redemption_percent'); // 單筆
Setting::group('shipping.'); // 整群
shipping.、points.、invoice.——用點號當命名空間,後台一個頁面就抓一群:
$shipping = Setting::group('shipping.');
不需要 category 欄位、不需要關聯表。命名本身就是結構。
type 欄位負責轉型value 一律存字串,但讀出來要是正確型別,不然 "0" 會變成 true,金額會變成字串相加:
'boolean' => filter_var($value, FILTER_VALIDATE_BOOLEAN),
'integer' => (int) $value,
'json' => json_decode($value ?? '[]', true) ?? [],
default => $value,
其中 json 這個型別很好用。像「海外配送區」這種一組一組的資料(區名、承運商、運費),我就存成一筆 json 設定,不用為它開一張表。
判準是:這份資料會不會被查詢、被關聯、被統計? 會的話(商品、訂單、折扣碼),開正規的表。只是拿來讀出來用的(運費、門檻、開關),放 settings。
Setting::get() 我刻意不提供預設值:
if ($setting === null) {
throw new RuntimeException("設定值不存在:{$key}");
}
很多人會寫 Setting::get('shipping.fee', 0),覺得比較安全。
那一點都不安全。設定漏掉的時候,你會拿 0 去算運費,然後一整天的訂單都免運,而且沒有任何錯誤訊息。 你會在對帳的時候才發現。
寧可讓它當場拋例外。開發階段兩分鐘就修好,總比上線之後賠錢好。
會影響金額的設定,一律 fail fast,不要猜。