高级

模块化架构(DDD)

简介

使用 Filament 构建大型应用时,你可能希望使用领域驱动设计(DDD)原则组织代码,将应用拆分为自包含的模块。本指南解释了如何将 Filament 与 InterNACHI/Modular 等模块化架构包集成。

模块化方式

在模块化架构中,应用的每个域都被构造为一个单独的 Composer 包,通常位于 app-modules/ 目录中。每个模块都包含自己的:

  • 模型和业务逻辑
  • Filament 资源、页面及 Widget
  • 服务提供者
  • 路由、视图和配置
  • 测试

该方式有多项优势:

  • 领域之间关注点清晰分离
  • 更易于团队协作(不同团队可负责不同模块)
  • 更好的可测试性和可维护性
  • 能够在不同项目间复用模块

设置 InterNACHI/Modular

首先,安装模块化包:

composer require internachi/modular

然后创建新模块:

php artisan make:module alerts

这会搭建出一个模块结构:

.
+-- app-modules
|   +-- alerts
|   |   +-- composer.json
|   |   +-- src
|   |   |   +-- Providers
|   |   |   |   +-- AlertsServiceProvider.php
|   |   +-- routes
|   |   +-- resources
|   |   +-- database
|   |   +-- tests

配置模块的 composer.json

每个模块都应导入 filament/filament 并定义其服务提供者:

{
    "name": "my-app/alerts",
    "type": "library",
    "require": {
        "filament/filament": "^5.0"
    },
    "autoload": {
        "psr-4": {
            "Modules\\Alerts\\": "src/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "Modules\\Alerts\\Providers\\AlertsServiceProvider"
            ]
        }
    }
}

为你的模块创建 Filament 插件

每个模块应该定义它自己的 Filamen 插件,该插件注册模块的资源、页面和 Widget:

namespace Modules\Alerts;

use Filament\Contracts\Plugin;
use Filament\Panel;

class AlertsPlugin implements Plugin
{
    public function getId(): string
    {
        return 'alerts';
    }

    public static function make(): static
    {
        return app(static::class);
    }

    public function register(Panel $panel): void
    {
        $panel
            ->discoverResources(
                in: __DIR__ . '/Filament/Resources',
                for: 'Modules\\Alerts\\Filament\\Resources',
            )
            ->discoverPages(
                in: __DIR__ . '/Filament/Pages',
                for: 'Modules\\Alerts\\Filament\\Pages',
            )
            ->discoverWidgets(
                in: __DIR__ . '/Filament/Widgets',
                for: 'Modules\\Alerts\\Filament\\Widgets',
            );
    }

    public function boot(Panel $panel): void
    {
        //
    }
}

针对特定面板有条件地注册插件

当拥有多个面板(例如 admin、app、portal)时,通常希望某些模块仅针对特定面板注册其插件。此时,可以在模块的服务提供者(Service Provider)中使用 Panel::configureUsing() 来有条件地注册插件。

基础条件注册

若要为除某个面板以外的所有面板注册插件:

namespace Modules\Alerts\Providers;

use Filament\Panel;
use Illuminate\Support\ServiceProvider;
use Modules\Alerts\AlertsPlugin;

class AlertsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        Panel::configureUsing(function (Panel $panel): void {
            if ($panel->getId() !== 'admin') {
                return;
            }

            $panel->plugin(AlertsPlugin::make());
        });
    }
}

针对多个面板使用 match 语句

当需要为特定面板注册插件,或针对不同面板进行不同配置时,请使用直接调用 $panel->plugin() 的 match 语句:

namespace Modules\Alerts\Providers;

use Filament\Panel;
use Illuminate\Support\ServiceProvider;
use Modules\Alerts\AlertsPlugin;

class AlertsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        Panel::configureUsing(function (Panel $panel): void {
            match ($panel->getId()) {
                'admin' => $panel->plugin(
                    AlertsPlugin::make()->enableAdminFeatures(),
                ),
                'staff' => $panel->plugin(
                    AlertsPlugin::make(),
                ),
                default => null,
            };
        });
    }
}

这种方法允许你根据面板的不同来配置各个插件实例,而未在语句中匹配到的面板则不会加载该插件。

模块目录结构

一个结构合理且集成了 Filament 的模块,其目录结构可能如下所示:

.
+-- app-modules
|   +-- alerts
|   |   +-- composer.json
|   |   +-- config
|   |   |   +-- alerts.php
|   |   +-- database
|   |   |   +-- factories
|   |   |   +-- migrations
|   |   |   +-- seeders
|   |   +-- resources
|   |   |   +-- views
|   |   |   |   +-- filament
|   |   |   |   |   +-- pages
|   |   +-- routes
|   |   |   +-- web.php
|   |   +-- src
|   |   |   +-- AlertsPlugin.php
|   |   |   +-- Filament
|   |   |   |   +-- Pages
|   |   |   |   +-- Resources
|   |   |   |   |   +-- Alerts
|   |   |   |   |   |   +-- AlertResource.php
|   |   |   |   |   |   +-- Pages
|   |   |   |   |   |   |   +-- CreateAlert.php
|   |   |   |   |   |   |   +-- EditAlert.php
|   |   |   |   |   |   |   +-- ListAlerts.php
|   |   |   |   +-- Widgets
|   |   |   +-- Models
|   |   |   |   +-- Alert.php
|   |   |   +-- Providers
|   |   |   |   +-- AlertsServiceProvider.php
|   |   +-- tests

在面板间共享资源

有时,你可能希望同一个资源以不同的配置显示在多个面板中。你可以通过结合使用资源发现功能与针对特定面板的自定义设置来实现这一点:

namespace Modules\Users;

use Filament\Contracts\Plugin;
use Filament\Panel;
use Modules\Users\Filament\Resources\UserResource;

class UsersPlugin implements Plugin
{
    protected bool $canManageRoles = false;

    public function getId(): string
    {
        return 'users';
    }

    public static function make(): static
    {
        return app(static::class);
    }

    public function canManageRoles(bool $condition = true): static
    {
        $this->canManageRoles = $condition;

        return $this;
    }

    public function hasRoleManagement(): bool
    {
        return $this->canManageRoles;
    }

    public function register(Panel $panel): void
    {
        $panel->resources([
            UserResource::class,
        ]);
    }

    public function boot(Panel $panel): void
    {
        //
    }
}

然后,使用不同的功能进行注册:

Panel::configureUsing(function (Panel $panel): void {
    match ($panel->getId()) {
        'admin' => $panel->plugin(
            UsersPlugin::make()->canManageRoles(),
        ),
        'staff' => $panel->plugin(
            UsersPlugin::make(),
        ),
        default => null,
    };
});

NOTE

这种 Panel::configureUsing() 方式非常强大,因为它允许模块自行配置,而无需修改你的面板服务提供者文件。当你添加或移除模块时,系统会自动处理其与 Filament 的集成。

在模块中注册 Livewire 组件

如果你的模块包含 Filament 使用的自定义 Livewire 组件(例如自定义页面或 Widget),你可以在插件的 boot() 方法中注册它们:

use Livewire\Livewire;
use Modules\Alerts\Filament\Pages\AlertsDashboard;

public function boot(Panel $panel): void
{
    Livewire::component('alerts-dashboard', AlertsDashboard::class);
}
Edit on GitHub

Still need help? Join our Discord community or open a GitHub discussion

Previous
文件生成