使用 spatie/laravel-data 保存 Laravel 应用的设置
很长一段时间以来,我一直在努力寻找一种在我的 Laravel 应用中为用户、团队或任何其他模型存储设置的好方法。在过去的10年里,我使用了不同的方法来解决这个问题。
要么在相应的数据库表格中每个设置添加特定的字段列(比如,在 user 表中添加 timezone 或 date_format 字段);或者在 user 表中添加通用的设置 JSON 字段,来存储设置。
每个方法都有其优点和缺点。如果需要通过特定设置查询(比如,查询哪个用户有自定义日期格式如 YYYY-MM-DD),添加单独的列是不错的方案。另一方面,仅为一个小的设置添加一个新字段可能会过剩,尤其是当表中有数百万或数十亿行时。
将设置字段与核心表分离可以更好地提高数据库性能。数据库不必一直加载所有设置字段,这在你需要数据库中的用户或项目列表时非常有用。
添加宽泛的设置字段是最简单的方式,不过可能带来性能问题,并且使行中出现不同结构。如果预期的设置不存在于用户中,应用如何响应?你是否不得不在每个地方检查设置的 JSON 键是否存在(比如,$user->settings?->my_custom_setting
)?
我的方案是,使用 spatie/laravel-data 包
spatie/laravel-data
该 laravel-data 包的主要用例是,在 Laravel 项目中创建强类型数据对象。以下是这样的数据对象的示例:
use Spatie\LaravelData\Data;
class SongData extends Data
{
public function __construct(
public string $title,
public Artist $artist,
) {
}
}
该包也支持 Eloquent 转型,这意味着数据对象可以保存到数据端,并且当检索时转换会强类型数据实例。
强类型和 Eloquent 转型结合启发我使用该包保存应用设置。
示例
这是一个 UserSettings
对象的用例。
<?php
namespace App\Data;
use App\Domain\Support\Enums\ThemeApperance;
use Spatie\LaravelData\Data;
class UserSettings extends Data
{
public function __construct(
public string $timezone = 'UTC',
public string $locale = 'en',
public string $date_format = 'YYYY-MM-DD',
public ThemeApperance $apperance = ThemeApperance::AUTO,
) {
//
}
}
在这些设置中,存储看 timezone、locale 及偏好的日期格式,以及用户的主题外观。
创建完添加 settings 字段到 users 表的迁移后,更新 User 模型,使 settings 转型为 UserSettings
实例。
use App\Data\UserSettings;
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'settings' => UserSettings::class . ':default',
];
现在,多亏了 laravel-data,无论何时 $user->settings
总能获取 UserSettings
实例中所有的属性。
如果用户在两年前登录,且该应用不支持 $date_format
,用户中的这个值将回退到我在 UserSettings
类中声明的默认值。
下次用户更新设置时,过期的数据库状态将得到更新。不再支持的设置也将被移除。
如果你不想等待用户更新设置,你可以创建 Artisan 命令来帮你更新。
use App\Data\UserSettings;
use App\Models\User;
Artisan::command('app:update-user-settings', function () {
// Get all Users and update their settings
User::query()
->each(function (User $user) {
// Update settings to the newest format
$user->settings = UserSettings::from($user->settings);
$user->save();
});
});
该命令循环迭代数据库中的所有用户,并使用一个新版本更新 settings 字段。用户的当前设置(比如,他们选择的 $date_formate
)将被迁移。
为什么这样
起初,我提到的添加一个宽泛的 settings 字段,如果没有一个结构化的方式来存储设置,可能不是一个好主意。如果应用的不同部分中添加了新的键到设置中,可能很快就变得混乱了。而使用 laravel-data,该问题得以解决。设置只有一个来源。
你可以使用类型提示和枚举让设置强类型。你甚至可以创建嵌套结构的设置。
假设 UserSettings
类包含 UserGeneralSettings
、UserNotificationSettings
和 UserApperanceSettings
。
<?php
namespace App\Data;
use App\Domain\Support\Enums\ThemeApperance;
use Spatie\LaravelData\Data;
class UserSettings extends Data
{
public function __construct(
public UserGeneralSettings $general,
public UserNotificationSettings $notification,
public UserApperanceSettings $apperance,
) {
//
}
}
需要记住的是,对特定设置的查询可能带来性能问题,或许应该避免。
如果应用通常需要查询选择特定的 date_formate 的用户,最好将该设置提升为单独的字段。这可能会让数据库的工作及索引更为容易。
结论
展望未来,我将在所有需要设置概念的新应用和现有应用中使用这种方法。
我相信使用 spatie/laravel 数据和 Eloquent 类型转换比只将您的设置放入通用的 $settings
数组要好。我鼓励你在下一个项目中尝试一下。
你喜欢这种方法吗?还是觉得这不是个好主意呢?欢迎留言。