matteoc99 / laravel-preference
Laravel 包,旨在以简单和可扩展的方式存储和管理用户设置/偏好
Requires
- php: ^8.1
- ext-pdo: *
- doctrine/dbal: ^3.8
- laravel/framework: ^10 | ^11
Requires (Dev)
- orchestra/testbench: ^8.0
- phpunit/phpunit: ^10.0
Suggests
- graham-campbell/security-core: Required to use xss cleaning.(^4.0)
This package is auto-updated.
Last update: 2024-09-07 20:52:53 UTC
README
此 Laravel 包旨在以简单和可扩展的方式存储和管理用户设置/偏好。
目录
功能
- 类型安全的类型转换
- 验证与授权
- 可扩展的(创建你自己的验证规则和类型转换)
- 枚举支持
- 自定义 API 路由
- 与 GUI 或后端功能一起使用偏好
路线图
安装
您可以通过 composer 安装此包
composer require matteoc99/laravel-preference
重要
考虑安装 graham-campbell/security-core:^4.0
以利用 XSS 清理。有关更多信息,请参阅 安全
配置
您可以使用以下命令发布配置文件
php artisan vendor:publish --tag="laravel-preference-config"
'db' => [ 'connection' => null, //string: the connection name to use 'preferences_table_name' => 'preferences', 'user_preferences_table_name' => 'user_preferences', ], 'xss_cleaning' => true, // clean user input for cross site scripting attacks 'routes' => [ 'enabled' => false, // set true to register routes, more on that later 'middlewares' => [ 'auth', // general middleware 'user'=> 'verified', // optional, scoped middleware 'user.general'=> 'verified' // optional, scoped & grouped middleware ], 'prefix' => 'preferences', 'groups' => [ //enum class list of preferences 'general'=>General::class ], 'scopes'=> [ // as many preferenceable models as you want 'user' => \Illuminate\Auth\Authenticatable::class ] ]
注意
如果需要,请在运行迁移之前考虑更改基础表名称
使用以下命令运行迁移
php artisan migrate
使用方法
概念
每个偏好至少有一个名称和一个类型转换器。名称存储在一个或多个枚举中,并是该偏好的唯一标识符
对于额外的验证,您可以添加您自定义的规则对象。
对于额外的安全性,您可以添加策略
定义你的偏好
将它们组织在一个或多个 字符串支持的 枚举中。
注意
尽管它不需要是字符串支持的,但它对开发者来说更加友好。尤其是当通过 API 交互时
每个枚举都会进行范围限制,不会与其他相同情况的枚举冲突
例如:
use Matteoc99\LaravelPreference\Contracts\PreferenceGroup; enum Preferences :string implements PreferenceGroup { case LANGUAGE="language"; case QUALITY="quality"; case CONFIG="configuration"; } enum General :string implements PreferenceGroup { case LANGUAGE="language"; case THEME="theme"; }
创建偏好
单模式
use Matteoc99\LaravelPreference\Enums\Cast; public function up(): void { PreferenceBuilder::init(Preferences::LANGUAGE) ->withDefaultValue("en") ->withRule(new InRule("en", "it", "de")) ->create(); // Or PreferenceBuilder::init(Preferences::LANGUAGE)->create() // different enums with the same value do not conflict PreferenceBuilder::init(General::LANGUAGE)->create() // update PreferenceBuilder::init(Preferences::LANGUAGE) ->withRule(new InRule("en", "it", "de")) ->updateOrCreate() // or with casting PreferenceBuilder::init(Preferences::LANGUAGE, Cast::ENUM) ->withDefaultValue(Language::EN) ->create() // nullable support PreferenceBuilder::init(Preferences::LANGUAGE, Cast::ENUM) ->withDefaultValue(null) ->nullable() ->create() } public function down(): void { PreferenceBuilder::delete(Preferences::LANGUAGE); }
批量模式
use Illuminate\Database\Migrations\Migration;use Matteoc99\LaravelPreference\Enums\Cast;use Matteoc99\LaravelPreference\Factory\PreferenceBuilder;use Matteoc99\LaravelPreference\Rules\InRule; return new class extends Migration { public function up(): void { PreferenceBuilder::initBulk($this->preferences(), true // nullable for the whole Bulk ); } /** * Reverse the migrations. */ public function down(): void { PreferenceBuilder::deleteBulk($this->preferences()); } /** * Reverse the migrations. */ public function preferences(): array { return [ ['name' => Preferences::LANGUAGE, 'cast' => Cast::STRING, 'default_value' => 'en', 'rule' => new InRule("en", "it", "de")], ['name' => Preferences::THEME, 'cast' => Cast::STRING, 'default_value' => 'light'], ['name' => Preferences::CONFIGURATION, 'cast' => Cast::ARRAY], ['name' => Preferences::CONFIGURATION, 'nullable' => true // or nullable for only one configuration ], // or an array of initialized single-mode builders PreferenceBuilder::init(Preferences::LANGUAGE)->withRule(new InRule("en", "it", "de")), PreferenceBuilder::init(Preferences::THEME)->withRule(new InRule("light", "dark")) //mixing both in one array is also possible ]; } };
偏好构建
检查构建偏好的所有可用方法
可用方法
此表包括构建偏好时所有可用功能的完整列表。
可用辅助函数
可选地,将默认值作为第二个参数传递
// quickly build a nullable Array preference PreferenceBuilder::buildArray(VideoPreferences::CONFIG); PreferenceBuilder::buildString(VideoPreferences::LANGUAGE);
与偏好协同工作
需要两件事
HasPreferences
特性以访问辅助函数PreferenceableModel
接口以访问实现- 特别是
isUserAuthorized
- 特别是
isUserAuthorized
守卫函数,用于验证当前登录(如果有的话)的用户是否可以访问此模型 签名
- $user 登录用户
- PolicyAction 枚举:用户想要执行的操作索引/获取/更新/删除
注意
这只是关于授权的最基本内容。
有关更精细的授权检查,请参阅 策略
示例实现
use Matteoc99\LaravelPreference\Contracts\PreferenceableModel; use Matteoc99\LaravelPreference\Enums\PolicyAction; use Matteoc99\LaravelPreference\Traits\HasPreferences; class User extends \Illuminate\Foundation\Auth\User implements PreferenceableModel { use HasPreferences; protected $fillable = ['email']; public function isUserAuthorized(?Authenticatable $user, PolicyAction $action): bool { return $user?->id == $this->id ; } }
示例
$user->setPreference(Preferences::LANGUAGE,"de"); $user->getPreference(Preferences::LANGUAGE); // 'de' as string $user->setPreference(Preferences::LANGUAGE,"fr"); // ValidationException because of the rule: ->withRule(new InRule("en","it","de")) $user->setPreference(Preferences::LANGUAGE,2); // ValidationException because of the cast: Cast::STRING $user->removePreference(Preferences::LANGUAGE); $user->getPreference(Preferences::LANGUAGE); // 'en' as string // get all of type Preferences, $user->getPreferences(Preferences::class) // or of type general $user->getPreferences(General::class) //or all $user->getPreferences(): Collection of UserPreferences // removes all preferences set for tht user $user->removeAllPreferences();
类型转换
在创建偏好时设置类型转换
注意
类型转换主要有三个任务
- 基本验证
- 数据库的转换
- 准备API响应
示例
PreferenceBuilder::init(Preferences::LANGUAGE, Cast::ENUM)
可用的类型转换
自定义类型转换器
实现CastableEnum
重要
自定义转换器需要是基于字符串
的枚举
示例
use Illuminate\Contracts\Validation\ValidationRule; use Matteoc99\LaravelPreference\Contracts\CastableEnum; enum MyCast: string implements CastableEnum { case TIMEZONE = 'tz'; public function validation(): ValidationRule|array|string|null { return match ($this) { self::TIMEZONE => 'timezone:all', }; } public function castFromString(string $value): mixed { return match ($this) { self::TIMEZONE => $value, }; } public function castToString(mixed $value): string { return match ($this) { self::TIMEZONE => (string)$value, }; } public function castToDto(mixed $value): array { return ['value' => $value]; } } PreferenceBuilder::init(Preferences::TIMEZONE, MyCast::TIMEZONE)->create();
规则
额外的验证,可能比提供的转换更复杂
添加规则
PreferenceBuilder::init(General::VOLUME, Cast::INT) ->withRule(new LowerThanRule(5)) ->updateOrCreate() PreferenceBuilder::initBulk([ 'name' => General::VOLUME, 'cast' => Cast::INT 'rule' => new LowerThanRule(5) ]);
可用的规则
自定义规则
实现Laravel的ValidationRule
示例
class MyRule implements ValidationRule { protected array $data; public function __construct(...$data) { $this->data = $data; } public function message() { return sprintf("Wrong Timezone, one of: %s expected", implode(", ",$this->data)); } public function validate(string $attribute, mixed $value, Closure $fail): void { if(!Str::startsWith($value, $this->data)){ $fail($this->message()); } } } PreferenceBuilder::init("timezone",MyCast::TIMEZONE) ->withRule(new MyRule("Europe","Asia"))
策略
每个偏好都可以有一个策略,如果isUserAuthorized
不足以满足你的用例
创建策略
实现PreferencePolicy
和合同定义的4个方法
添加策略
PreferenceBuilder::init(Preferences::LANGUAGE) ->withPolicy(new MyPolicy()) ->updateOrCreate() PreferenceBuilder::initBulk([ 'name' => Preferences::LANGUAGE, 'policy' => new MyPolicy() ]);
路由
默认关闭,在配置中启用
警告
(当前)限制:无法通过API设置对象转换
解剖学
'Scope': PreferenceableModel
模型
'Group': PreferenceGroup
枚举
然后路由被转换成
可以通过路由名称:{prefix}.{scope}.{group}.{index/get/update/delete}访问
URI参数
scope_id
:范围的唯一标识符(例如,用户的ID)。
preference
:特定偏好枚举的值(例如,General::LANGUAGE->value)。
group
:将组名映射到相应的枚举类。请参阅下面的配置
scope
:将范围名称映射到相应的Eloquent模型。请参阅下面的配置
配置示例
'routes' => [ 'enabled' => true, 'middlewares' => [ 'auth', 'user'=> 'verified' ], 'prefix' => 'custom_prefix', 'groups' => [ 'general'=>\Matteoc99\LaravelPreference\Tests\TestSubjects\Enums\General::class 'video'=>\Matteoc99\LaravelPreference\Tests\TestSubjects\Enums\VideoPreferences::class ], 'scopes'=> [ 'user' => \Matteoc99\LaravelPreference\Tests\TestSubjects\Models\User::class ] ]
将产生以下路由名称
- custom_prefix.user.general.index
- custom_prefix.user.general.get
- custom_prefix.user.general.update
- custom_prefix.user.general.delete
- custom_prefix.user.video.index
- custom_prefix.user.video.get
- custom_prefix.user.video.update
- custom_prefix.user.video.delete
动作
注意
示例使用范围user
和组general
索引
- 路由名称:custom_prefix.user.general.index
- URL参数:
scope_id
- 等同于:
$user->getPreferences(General::class)
- HTTP方法:GET
- 端点:https://your.domain/custom_prefix/user/{scope_id}/general
GET
- 路由名称:custom_prefix.user.general.get
- URL参数:
scope_id
,preference
- 等同于:
$user->getPreference(General::{preference})
- HTTP方法:GET
- 端点:https://your.domain/custom_prefix/user/{scope_id}/general/{preference}
更新
- 路由名称:custom_prefix.user.general.update
- URL参数:
scope_id
,preference
- 等同于:
$user->setPreference(General::{preference}, >value<)
- HTTP方法:PATCH/PUT
- 端点:https://your.domain/custom_prefix/user/{scope_id}/general/{preference}
- 负载:
{ "value": >value< }
枚举修补
当创建你的枚举偏好时,添加包含可能枚举的setAllowedClasses
以重建值
注意
如果多个枚举之间存在共享的多个情况,则取第一个匹配项
然后,发送值时会有所不同
- BackedEnum:发送值或情况
- UnitEnum:发送情况
示例
enum Theme { case LIGHT; case DARK; } curl -X PATCH 'https://your.domain/custom_prefix/user/{scope_id}/general/{preference}' \ -d '{"value": "DARK"}'
删除
- 路由名称:(custom_prefix.user.general.delete)
- URL参数:
scope_id
,preference
- 等同于:
$user->removePreference(General::{preference})
- HTTP方法:DELETE
- 端点:https://your.domain/custom_prefix/user/{scope_id}/general/{preference}
中间件
在配置文件中设置全局或上下文特定的中间件
'middlewares' => [ 'web', // required for Auth::user() and policies 'auth', //no key => general middleware which gets applied to all routes 'user'=> 'verified', // scoped middleware only for user routes should you have other preferencable models 'user.general'=> 'verified' // scoped & grouped middleware only for a specific model + enum ],
注意
已知问题:没有web中间件,你将无法通过Auth外观访问用户,因为它是通过中间件设置的。正在寻找替代方案
安全
XSS清理仅在对用户可见的API调用上执行。可以通过配置禁用,如果不要求的话:user_preference.xss_cleaning
直接通过setPreference
设置偏好时,假定已执行了此清理步骤(如果需要的话)。
考虑安装Security-Core以使用此功能
从 v1 升级
- 在你的偏好枚举中实现
PreferenceGroup
- 在所有希望使用偏好的模型中实现
PreferenceableModel
- 从
HasValidation
切换到ValidationRule
- 特性行为签名变更:组别已被移除,名称现在需要
PreferenceGroup
- Builder:移除了设置组别,名称现在期望一个
PreferenceGroup
枚举 DataRule
已被移除,添加一个构造函数以获取您自己的定制参数- 数据库序列化不兼容性将需要您重新运行偏好迁移
测试
composer 测试
composer 覆盖率
在本地测试管道
然后运行:composer pipeline
贡献
有关详细信息,请参阅 贡献指南
安全漏洞
请审查 我们的安全策略 了解如何报告安全漏洞。
鸣谢
- matteoc99
- 感谢 Joel Brown 为 这个 令人惊叹的起点和初步灵感
许可证
MIT 许可证 (MIT)。请检查 许可文件 以获取更多信息。