poisa/settings

Laravel多租户数据库配置/设置管理器

0.1.13 2018-08-14 17:01 UTC

This package is not auto-updated.

Last update: 2024-09-29 06:02:27 UTC


README

Build Status codecov Maintainability SensioLabsInsight

Settings:一个Laravel 5多租户设置管理器

包目标

能够在单租户或多租户环境中将自定义配置项存储在数据库中,无论是纯文本、加密形式,还是任何其他可定制的格式。在此,配置项不是指Laravel的配置,而是指您特定领域的配置。

以下三种特定场景下,此包可能非常有用

  1. 多租户系统,您将代码部署到一台服务器,根据域名规则连接到不同的租户数据库(例如,不同的用户连接到不同的数据库)。
  2. 与#1相同,但您添加了一个始终连接的主要数据库。以CMS为例,您有CMS自己的数据库(称为系统数据库),然后您还连接到每个客户的数据库(称为租户数据库)。在这种情况下,您可以同时使用这两个数据库。
  3. 只有一个数据库的单租户网站。

当您需要存储奇特的非必需数据,而不想为它们创建单独的表时,此包特别有用。

将此包视为一个了解数据类型(包括自定义类型)的键值存储,它可以在静态数据中加密数据,并且还可以触发事件。

索引

发行说明

  • 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一起工作的序列化程序。这意味着我们需要创建并注册一个新的序列化程序,以便设置可以知道如何与这个类一起工作。

因此,为了教设置如何处理您自己的数据类型,您需要

  1. 创建一个序列化程序类。
  2. 将其注册到设置中。
创建一个序列化程序类

序列化程序类只是实现了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 将键保存到数据库

注意:使用命令行保存键只能存储字符串和数值。存储其他类型(包括自定义类型)仅可以通过代码实现。