支持Laravel.io的持续发展 →

一个不带偏见的Laravel应用程序客户端意识包

2020年6月24日 9分钟阅读 123次查看

今天我们发布了一个名为 laravel-multitenancy 的包,用于使Laravel应用具备多租户功能。这个包的理念是仅提供实现多租户功能的最基本功能。

该包可以确定请求的当前租户应该是什么。它还允许你定义当将当前租户更改为另一个租户时应发生什么。

该包适用于需要使用单一数据库或多个数据库的 一个多个数据库 的多租户项目。

在这篇博客文章中,我想向大家介绍这个包。

您是视觉学习者吗?

Laravel Package Training 包含一个20分钟的教程视频,它将引导您通过一个多数据库演示应用,该应用使用 laravel-multitenancy。展示该包的功能后,我将解释其内部工作原理。

我非常相信,观看这个视频每个人都能学到一些新东西。

为什么创建另一个多租户包?#

Laravel中的多租户制似乎一直是热门话题。我认为这是因为实现多租户制的途径有很多。为了了解多租户制能够包含的内容以及可能的解决方案,我强烈推荐观看Tom Schlick在2017年Laracon US上所做的这场演讲

因为我在客户项目中从未需要过,所以我一直避开这个话题。

有人可能会说,我构建的Oh Dear(uptime tracker),就是一个支持多租户制的项目。我们在那里使用了非常轻量级的解决方案。我们只是在sites表中添加了一个team_id。当有人登录时,我们会检查用户所属的团队,并只为这些站点显示信息。我认为对于大多数项目来说,这样的单一数据库解决方案效果很好。

最近,我开始着手一个新的客户项目,这个项目应该是多租户制的。这个项目的需求表明,每个租户应该有自己的数据库。在我研究这个话题的时候,穆罕默德·萨伊德(Mohammed Said)发布了一系列关于多租户制的视频,这真是一种纯粹的巧合。

在这些视频中,穆罕默德分享了一个非常轻量级的解决方案。这似乎表明多租户制并没有我想象的那么难。我决定打包他的解决方案。在这样做的同时,通过与穆罕默德的讨论,我对一个多租户包应该做什么有了更深入的了解。

大多数现有的包对我来说感觉太重了。我想知道为什么是这样。我认为任何多租户包都应该做这三件事:

  1. 应该跟踪当前租户是谁
  2. 在将一个租户设置为当前租户时应动态更改Laravel应用程序的配置(更改数据库、前缀缓存)
  3. 例如,为租户创建新数据库的工具,或者迁移租户的工具

对我来说,现有的包做的事情太多了。它们中的大多数在1.方面做得很好,但也过于关注“2.”和“3.”。

我认为在需要租户意识的项目中,每个项目都需要做一些特定的事情来将租户设置为当前租户。我们不尝试处理所有不同的案例,而是决定使定义使租户成为当前租户的任务变得简单。这样,实现上面的“2。”保持非常轻量。

对于“3.”工具,我的同事Seb有一个很好的想法。我们并不是创建特定的租户命令,而是让现存的命令容易地感知租户。这就是我们做到的。在本文的后续部分,我会解释这个解决方案。

通过将“2.”和“3.”olutions保持非常通用,包保持了轻量。

跟踪当前租户

您安装包后,应用程序将有一个包含每个应用程序租户的行的tenants表。

为了确定给定请求中应使用哪个租户作为当前租户,该软件包使用TenantFinder。任何扩展Spatie\Multitenancy\TenantFinder\TenantFinder的类都是有效的租户查找器。这个抽象类的样子是这样的

abstract public function findForRequest(Request $request): ?Tenant;

该软件包附带一个名为DomainTenantFinder的类。该类将尝试找到具有匹配当前请求主机名的domain属性的Tenant

以下是默认的DomainTenantFinder的实现。该getTenantModel方法返回在multitenancy配置文件中的tenant_model键指定的类的实例。

namespace Spatie\Multitenancy\TenantFinder;

use Illuminate\Http\Request;
use Spatie\Multitenancy\Models\Concerns\UsesTenantModel;
use Spatie\Multitenancy\Models\Tenant;

class DomainTenantFinder extends TenantFinder
{
    use UsesTenantModel;

    public function findForRequest(Request $request):?Tenant
    {
        $host = $request->getHost();

        return $this->getTenantModel()::whereDomain($host)->first();
    }
}

multitenancy 配置文件中,您可以使用 tenant_finder 键指定租户查找器。

// in multitenancy.php
/*
 * This class is responsible for determining which tenant should be current
 * for the given request.
 *
 * This class should extend `Spatie\Multitenancy\TenantFinder\TenantFinder`
 *
 */
'tenant_finder' => Spatie\Multitenancy\TenantFinder\DomainTenantFinder::class,

这使得自定义如何确定当前租户变得更加容易。

有几种方法可以获取、设置和清除当前租户。

您可以通过这种方式找到当前方法。

Spatie\Multitenancy\Models\Tenant::current(); // returns the current tenant, or if not tenant is current, `null`

当前租户还会使用 currentTenant 键绑定在容器中。

app('currentTenant'); // returns the current tenant, or if not tenant is current, `null`

您可以检查是否已设置租户作为当前租户

Tenant::checkCurrent() // returns `true` or `false`

您可以通过调用其上的 makeCurrent() 方法来手动将租户设置为当前租户。

$tenant->makeCurrent();

定义应在将租户设置为当前租户时运行的作业

当租户被设置为当前租户时,该软件包将运行 multitenancy 配置文件中 switch_tenant_tasks 键配置的所有任务。

此软件包的哲学是它应仅提供启用多租户功能的基本功能。这就是为什么它只提供两个内置任务。这些任务作为示例实现。

让我们看看这两个任务中的一个:SwitchDatabaseTask。当您为您的每个租户使用单个数据库时,该任务才有用。

此任务可以切换 tenant 数据库连接配置的数据库名称。将使用的数据库名称将在 Tenant 模型的 database 属性中。

当使用为每个租户单独数据库时,您的 Laravel 应用程序需要两个数据库连接。一个命名为 landlord,指向包含 tenants 表和其他系统相关信息的数据库。另一个连接,名为 tenant,指向作为请求当前租户的租户的数据库。

database 配置文件中可能会是这样的。

// in config/database.php

'connections' => [
   'tenant' => [
       'driver' => 'mysql',
       'database' => null,
       // other options such as host, username, password, ...
   ],

   'landlord' => [
       'driver' => 'mysql',
       'database' => 'name_of_landlord_db',
       // other options such as host, username, password, ...
   ],

您会发现,tenant 连接的 database 键设置为 null。当将租户设置为当前租户时,SwitchDatabaseTask 将自动将此 database 键设置为租户的 database 属性中的数据库名称。

这是 SwitchDatabaseTask 的样子。当将租户设置为当前租户时,将调用 makeCurrent 方法。

namespace Spatie\Multitenancy\Tasks;

use Illuminate\Support\Facades\DB;
use Spatie\Multitenancy\Concerns\UsesMultitenancyConfig;
use Spatie\Multitenancy\Exceptions\InvalidConfiguration;
use Spatie\Multitenancy\Models\Tenant;

class SwitchTenantDatabaseTask implements SwitchTenantTask
{
    use UsesMultitenancyConfig;

    public function makeCurrent(Tenant $tenant): void
    {
        $this->setTenantConnectionDatabaseName($tenant->getDatabaseName());
    }

    public function forgetCurrent(): void
    {
        $this->setTenantConnectionDatabaseName(null);
    }

    protected function setTenantConnectionDatabaseName(?string $databaseName)
    {
        $tenantConnectionName = $this->tenantDatabaseConnectionName();

        if (is_null(config("database.connections.{$tenantConnectionName}"))) {
            throw InvalidConfiguration::tenantConnectionDoesNotExist($tenantConnectionName);
        }

        config([
            "database.connections.{$tenantConnectionName}.database" => $databaseName,
        ]);

        DB::purge($tenantConnectionName);
    }
}

创建自己的任务很简单。创建任务。任何实现了 Spatie\Multitenancy\Tasks\SwitchTenantTask 的类都是一个任务。以下是如何查看该接口。

namespace Spatie\Multitenancy\Tasks;

use Spatie\Multitenancy\Models\Tenant;

interface SwitchTenantTask
{
    public function makeCurrent(Tenant $tenant): void;

    public function forgetCurrent(): void;
}

当将租户设置为当前租户时,将调用 makeCurrent 函数。一个常见的是动态更改一些配置值。

创建任务后,您必须通过将类名放入 multitenancy 配置文件的 switch_tenant_tasks 键来注册它。

使 artisan 命令识别租户

如果您想为所有租户执行 artisan 命令,可以使用 tenants:artisan <artisan command>。此命令将遍历租户,对于每个租户,将执行 artisan 命令并使其成为当前租户。

当租户拥有自己的数据库时,您可以使用此命令迁移每个租户数据库(假设您正在使用类似 SwitchTenantDatabase 的任务)。

php artisan tenants:artisan migrate

总结

我认为我们包的最佳功能是其无边界的特性。通过完全不指定具体内容,并允许包的用户定义切换到租户时的所有所需行为,它保持了一段灵活性。

要了解更多关于我们包的信息,请查看详尽的文档。

我已在上面提到,我还创建了一个不错的视频,演示了如何使用我们的包使Laravel应用程序成为多租户。

如果您希望多租户包具有更多功能,请查看这些优秀的替代方案

我想感谢Mohammed Said。他的多租户视频启发我创建了我们的包。

laravel-multitenancy并不是我和我的团队创建的第一个包。这里有一个包含我们以前开源的所有项目的列表

最后更新1年前。

fermevc、driesvints、joedixon、ufukayyildiz 赞同了这篇文章

4
喜欢这篇文章吗?让作者知道,并给他们点赞!

你可能还喜欢这些文章

2024年3月11日

如何使用Larastan将您的Laravel应用程序从0扩展到9

由于Larastan,在您的Laravel应用程序执行之前发现错误是可能的...

阅读文章
2024年7月19日

在不使用特性的情况下标准化API响应

我发现大多数用于API响应的库都是使用特性实现的...

阅读文章
2024年7月17日

通过Discord通知在您的Laravel项目中收集反馈

如何在一个Laravel项目中创建反馈模块,并在收到消息时接收Discord通知...

阅读文章

我们感谢这些了不起的公司 对我们的大力支持

您的标志在这里?

Laravel.io

Laravel的解决问题、知识共享和社区建设的门户网站。

版权所有 © 2024 Laravel.io