poisa / settings
Laravel多租户数据库配置/设置管理器
Requires
- php: >=7.0.0
Requires (Dev)
- limedeck/phpunit-detailed-printer: dev-master
- orchestra/testbench: 3.5.x-dev
- phpunit/phpunit: 6.5.x-dev
README
Settings:一个Laravel 5多租户设置管理器
包目标
能够在单租户或多租户环境中将自定义配置项存储在数据库中,无论是纯文本、加密形式,还是任何其他可定制的格式。在此,配置项不是指Laravel的配置,而是指您特定领域的配置。
以下三种特定场景下,此包可能非常有用
- 多租户系统,您将代码部署到一台服务器,根据域名规则连接到不同的租户数据库(例如,不同的用户连接到不同的数据库)。
- 与#1相同,但您添加了一个始终连接的主要数据库。以CMS为例,您有CMS自己的数据库(称为系统数据库),然后您还连接到每个客户的数据库(称为租户数据库)。在这种情况下,您可以同时使用这两个数据库。
- 只有一个数据库的单租户网站。
当您需要存储奇特的非必需数据,而不想为它们创建单独的表时,此包特别有用。
将此包视为一个了解数据类型(包括自定义类型)的键值存储,它可以在静态数据中加密数据,并且还可以触发事件。
索引
发行说明
- 0.1.13 - 添加了忽略迁移的配置选项。对于想要自己管理迁移的人来说很有用。
- 0.1.12 - 初次发布。
安装
可以使用Composer自动执行安装。
composer require poisa/settings
接下来,您需要发布包配置。
php artisan vendor:publish
您将需要选择一个包。找到Poisa\Settings\SettingsServiceProvider
并选择它。这将在一个新的配置文件中创建一个新文件:config/settings.php
。在继续之前,您需要编辑它并选择适合您项目的配置选项。
现在,您需要执行包中包含的迁移。这些迁移将创建用于存储设置的数据库表。
php artisan migrate
重要:迁移将在您的默认数据库连接上运行。如果您只使用一个数据库,这很好,但如果您将在多个数据库中使用此包,则需要在所有数据库上运行迁移。
php artisan migrate --database=<your database>
或者,您可以使用自己的SQL管理软件将表复制到您需要的数据库和服务器上。
使用
最简单的用例假设您只有一个数据库,没有自定义数据类型。
<?php // Include the facade at the top of your file use Settings; // Set a key Settings::setKey('key', 123); // Get a key $value = Settings::getKey('key');
就像在任何其他键值存储中一样,Settings::setKey()
期望第一个参数是字符串键,第二个参数是要存储的值。
在多租户系统(如上面描述的场景#2)中运行时,Settings提供以下快捷方式
<?php use Settings; // Using the configured 'system' connection Settings::setSystemKey('key', 123); $value = Settings::getSystemKey('key'); // Using the configured 'tenant' connection Settings::setTenantKey('key', 123); $value = Settings::getTenantKey('key');
或者,您可以将连接名称作为最后一个参数传递
<?php use Settings; Settings::setKey('key', 123, 'sqlite'); $value = Settings::getKey('key', 'sqlite');
要检查键是否存在,可以使用hasKey()
方法
<?php use Settings; var_dump(Settings::hasKey('key')); // bool(false) Settings::setKey('key', 123); var_dump(Settings::hasKey('key')); // bool(true)
可能会出现您需要手动查询设置表的情况,但这可能很困难,因为不仅连接名称可以配置,表名也可以配置。设置提供了一个方法,可以为您提供一个正确配置的Eloquen模型,您可以使用它来手动查询或更改表。
<?php use Settings; // Using default connection name $eloquentModel = Settings::getConfiguredModel(); // Using custom connection name $eloquentModel = Settings::getConfiguredModel('mysql');
已知数据类型
默认情况下,设置可以存储以下类型
- 字符串
- 布尔值
- 双精度浮点数
- 整数
- 空值
- 数组
这意味着设置将存储和检索您给出的确切数据类型
<?php Settings::setKey('key', 123); var_dump(Settings::getKey('key') === 123); // bool(true) var_dump(Settings::getKey('key') === '123'); // bool(false) Settings::setKey('key', null); var_dump(Settings::getKey('key') === null); // bool(true) var_dump(Settings::getKey('key') === ''); // bool(false)
自定义数据类型
在某些情况下,存储简单类型可能不够。您还可以教设置与您的自定义数据类型一起工作。例如,假设您有一个用于存储用户偏好的类。为了简洁起见,让我们使用一个没有任何getter/setter和验证的非常简化的类
<?php class UserPreferences { public $backgroundColor; public $themeName; }
我们怎样才能做到这一点呢?
<?php use UserPreferences; use Settings; $prefs = new UserPreferences; $prefs->backgroundColor = '#3226D6'; $prefs->themeName = 'simple'; Settings::setKey('userPrefs', $prefs); // and then... $prefs = Settings::getKey('userPrefs'); var_dump(get_class($prefs) == UserPreferences::class); // bool(true)
如果您这样做,您将得到一个异常 Poisa\Settings\Exceptions\UnknownDataType
,消息为 没有注册可序列化以与UserPreferences一起工作的序列化程序
。这意味着我们需要创建并注册一个新的序列化程序,以便设置可以知道如何与这个类一起工作。
因此,为了教设置如何处理您自己的数据类型,您需要
- 创建一个序列化程序类。
- 将其注册到设置中。
创建一个序列化程序类
序列化程序类只是实现了Poisa\Settings\Serializers\Serializer
接口的常规类。建议这个类是一个独立类,其唯一目的是序列化和反序列化我们的自定义数据类型,但在现实中它可以是任何类,甚至是我们的数据类。
在这个例子中,我们将创建一个专用类。由于我们的数据类型类名为UserPreferences
,让我们将序列化程序类命名为UserPreferencesSerializer
。我们将将其放在根命名空间中,但您可能希望为所有序列化程序类创建一个命名空间,以保持事情整洁。
注意:Serializer接口中的所有方法都在源代码中进行了详细说明。为了简洁,以下示例中删除了所有方法注释,仅保留了与我们的实现相关的注释。
现在我们的类看起来像这样
<?php use Poisa\Settings\Serializers\Serializer; class UserPreferencesSerializer implements Serializer { public $backgroundColor; public $themeName; public function getTypes(): array { // Return the name of the data type (aka class) that this serializer knows how to serialize. If this serializer // is generic in nature and know how to serialize multiple classes then you can return an array with multiple // values. return [UserPreferences::class]; } public function getTypeAlias(): string { // This string gets saved in the database so that when we unserialize the row, we know what serializer class // to use to unserialize it. You could easily return the same as getType() and // it would work fine, except that you will want to decouple your class names from your database as much as // possible. If you return the name of the class here and in the future you rename your class to something // else, then you'd need to rename all the settings in the database to whatever your class is now named. // If you just return a simple string with something representative of what the value is instead of the class // name, then renaming the class will incur in no extra work. return 'user-preferences'; } public function shouldEncryptData(): bool { // Yes, we want Settings to encrypt our data at rest. return true; } public function serialize($data): string { // $data is the instance of UserPreferences we want to serialize. // Return a simple string that we can save in the database. return json_encode([ 'backgroundColor' => $data->backgroundColor, 'themeName' => $data->themeName ]); } public function unserialize($data) { // Take the string we stored with serialize() and reverse the process. $decodedData = json_decode($data); $prefs = new UserPreferences; $prefs->backgroundColor = $decodedData->backgroundColor; $prefs->themeName = $decodedData->themeName; return $prefs; } }
安全提示:我们使用json_encode/json_decode进行序列化,而不是使用PHP自己的serialize/unserialize函数。在调用unserialize时存在潜在的安全问题,您应该知道https://php.ac.cn/manual/en/function.unserialize.php。这可能不会必然影响您,但了解这一点很重要。话虽如此,您可以使用任何您想要的机制,因为设置不会对您施加任何限制。只要从Serializer::serialize()方法返回一个字符串,那就没问题。
将其注册到设置中
最后一步是将新的序列化程序类注册到设置包中。为此,编辑config/settings.php
并将新的序列化程序添加到serializers
键中
'serializers' => [ Poisa\Settings\Serializers\ScalarString::class, Poisa\Settings\Serializers\ScalarBoolean::class, Poisa\Settings\Serializers\ScalarDouble::class, Poisa\Settings\Serializers\ScalarInteger::class, Poisa\Settings\Serializers\ScalarNull::class, Poisa\Settings\Serializers\ArrayType::class, UserPreferencesSerializer::class, ],
就是这样。设置现在知道如何存储和检索UserPreferences了!
如您所见,设置已经知道如何处理许多数据类型。如果您想更改设置与特定数据类型一起工作的方式,您必须取消注册其序列化程序并注册自己的。
注意:如果您取消注册了默认数据类型,并尝试使用该数据类型设置键,设置将引发异常。
事件
设置根据其正在执行的操作触发不同类型的事件。您可以通过订阅这些事件在事件发生时得到通知。在需要知道数据库中发生的情况的情况下,这很有用。例如,您需要生成数据库中发生的一切的审计跟踪(每个读取、写入和更新)。
您可以在Laravel的EventServiceProvider中监听设置事件
<?php namespace App\Providers; use Illuminate\Support\Facades\Event; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Poisa\Settings\Events\SettingCreated; use Poisa\Settings\Events\SettingRead; use Poisa\Settings\Events\SettingUpdated; class EventServiceProvider extends ServiceProvider { public function boot() { parent::boot(); Event::listen(SettingCreated::class, function ($event) { // $event->key; // $event->value; // $event->connection; }); Event::listen(SettingUpdated::class, function ($event) { // $event->key; // $event->value; // $event->connection; }); Event::listen(SettingRead::class, function ($event) { // $event->key; // $event->value; // $event->connection; }); } }
注意:$event-value可以是您发送到设置的任何内容;一个标量值、数组或设置知道的任何类型的对象。在工作之前,请确保不要对值有任何假设。另外,请注意,当您在事件中接收到值时,它总是未序列化的。
CLI命令
设置程序自带了一些命令,可以帮助您从命令行检查设置表。拥有这些命令的原因是,当数据加密时,直接查看数据库表将不会非常有用。这些命令会为您执行解密。
要列出此包的所有可用命令,您可以运行
php artisan list settings
目前以下命令可用
- settings:get 从数据库获取一个键并将其输出到标准输出(如果需要,将对其进行解密)
- settings:set 将键保存到数据库
注意:使用命令行保存键只能存储字符串和数值。存储其他类型(包括自定义类型)仅可以通过代码实现。