可配置的资源与页面
简介
有时你需要以不同配置多次注册同一个资源或页面。例如,「订单」资源可能在侧边栏中同时显示为「进行中的订单」和「已归档订单」,各自使用不同的查询作用域、导航标签和 URL slug——但共用同一个底层资源类。
可配置的资源与页面允许你在面板中多次注册同一个类,每次注册都有唯一的配置键和各自的选项。每个配置都有自己的路由、导航项和 URL slug,而资源或页面类可使用当前激活的配置在运行时调整行为。
INFO
虽然本指南位于插件章节,但可配置的资源与页面可在任意 PanelProvider 中使用——不必正在构建插件。它们对插件尤其有用,因为能让插件作者向用户暴露灵活的配置。
创建资源配置类
要让资源可配置,首先需要一个配置类。该类继承 ResourceConfiguration,并定义各次注册之间可以不同的选项:
use Filament\Resources\ResourceConfiguration;
class OrderResourceConfiguration extends ResourceConfiguration
{
protected bool $isArchived = false;
protected ?string $navigationLabel = null;
protected ?string $navigationGroup = null;
public function archived(bool $condition = true): static
{
$this->isArchived = $condition;
return $this;
}
public function isArchived(): bool
{
return $this->isArchived;
}
public function navigationLabel(string $label): static
{
$this->navigationLabel = $label;
return $this;
}
public function getNavigationLabel(): ?string
{
return $this->navigationLabel;
}
public function navigationGroup(string $group): static
{
$this->navigationGroup = $group;
return $this;
}
public function getNavigationGroup(): ?string
{
return $this->navigationGroup;
}
}配置类遵循与 Filament 其余部分相同的 流畅 API 模式——setter 方法返回 $this 以便链式调用,getter 方法取出已存储的值。
TIP
ResourceConfiguration 基类已包含用于覆盖 URL slug 的 slug() 方法。你只需添加插件特有的属性。
将配置类关联到资源
在资源上设置 $configurationClass 属性,将其与配置类关联:
use Filament\Resources\Resource;
class OrderResource extends Resource
{
protected static ?string $configurationClass = OrderResourceConfiguration::class;
// ...
}这会在资源上启用 make() 方法,用于创建可注册到面板上的新配置实例。
在面板上注册配置
你可以使用 make() 方法注册一个或多个配置。每个配置都需要唯一的键:
use App\Filament\Resources\OrderResource;
public function panel(Panel $panel): Panel
{
return $panel
->resources([
// "Active orders" configuration
OrderResource::make('active')
->navigationLabel('Active Orders')
->navigationGroup('Orders'),
// "Archived orders" configuration
OrderResource::make('archived')
->navigationLabel('Archived Orders')
->navigationGroup('Orders')
->archived(),
]);
}每次已配置的注册都会获得自己的路由和导航项。配置键('active'、'archived')在内部用于识别每次注册。
若还需要默认(未配置)的注册,也可以把资源类本身与配置一起注册。这是可选的——若只需要配置,也可以只注册配置:
$panel->resources([
OrderResource::class, // Optional default registration
OrderResource::make('active'),
OrderResource::make('archived')
->archived(),
]);TIP
若正在构建 插件类,则应在 register(Panel $panel) 方法内注册配置。完整示例见 在插件类中使用可配置资源。
URL slug
单独注册资源类(不带配置)时,会使用资源的默认 URL slug——例如 /orders。
使用键注册配置时,该键会追加到资源的基础 slug 之后。例如,OrderResource::make('active') 可在 /orders/active 访问,OrderResource::make('archived') 则可在 /orders/archived 访问。
你可以使用 slug() 覆盖配置的整个 slug,而不使用默认的 {base}/{key} 模式:
OrderResource::make('archived')
->slug('order-archive') // accessible at `/order-archive` instead of `/orders/archived`
->archived(),在运行时使用配置
在资源类内部,调用 static::getConfiguration() 可获取当前请求的激活配置。当通过默认(未配置)注册访问资源时,该方法返回 null:
use Filament\Resources\Resource;
use Illuminate\Database\Eloquent\Builder;
use UnitEnum;
class OrderResource extends Resource
{
protected static ?string $configurationClass = OrderResourceConfiguration::class;
public static function getEloquentQuery(): Builder
{
$query = parent::getEloquentQuery();
if ($configuration = static::getConfiguration()) {
if ($configuration->isArchived()) {
$query->where('archived_at', '!=', null);
}
}
return $query;
}
public static function getNavigationLabel(): string
{
if ($configuration = static::getConfiguration()) {
if ($label = $configuration->getNavigationLabel()) {
return $label;
}
}
return parent::getNavigationLabel();
}
public static function getNavigationGroup(): string | UnitEnum | null
{
if ($configuration = static::getConfiguration()) {
if ($group = $configuration->getNavigationGroup()) {
return $group;
}
}
return parent::getNavigationGroup();
}
// ...
}你可以使用 static::hasConfiguration() 作为简写,检查当前是否有激活的配置:
if (static::hasConfiguration()) {
// Running inside a configured registration
}为特定配置生成 URL
为已配置的资源生成 URL 时,向 getUrl() 传入 configuration 参数:
// URL for the default (unconfigured) registration
OrderResource::getUrl();
// URL for the "active" configuration
OrderResource::getUrl(configuration: 'active');
// URL for a specific page within the "archived" configuration
OrderResource::getUrl('edit', ['record' => $order], configuration: 'archived');当你已处于已配置的请求中(例如在资源页面内)时,getUrl() 会自动使用当前配置上下文。仅在要链接到与当前不同的配置时,才需要传入 configuration 参数。
可配置页面
页面遵循与资源相同的模式。主要区别是:
- 配置类继承 `PageConfiguration` 而非 `ResourceConfiguration`
- 使用 `$panel->pages()` 而非 `$panel->resources()` 注册配置
- 由于页面是 Livewire 组件,可以在 `mount()` 中读取配置值:
use Filament\Pages\Page;
class SettingsPage extends Page
{
protected static ?string $configurationClass = SettingsPageConfiguration::class;
public function mount(): void
{
if ($configuration = static::getConfiguration()) {
$this->settingsCategory = $configuration->getSettingsCategory();
}
}
// ...
}$panel->pages([
SettingsPage::make('general')
->slug('general-settings')
->settingsCategory('general'),
SettingsPage::make('advanced')
->slug('advanced-settings')
->settingsCategory('advanced'),
]);临时切换配置上下文
你可以使用 withConfiguration() 在特定配置的上下文中执行代码。当你需要为当前激活配置以外的注册生成 URL 或访问配置值时,这会很有用:
$archivedUrl = OrderResource::withConfiguration('archived', function () {
return OrderResource::getUrl('index');
});在插件类中使用可配置资源
下面是一个向用户暴露可配置资源的插件完整示例:
use Filament\Contracts\Plugin;
use Filament\Panel;
class TasksPlugin implements Plugin
{
/** @var array<TaskResourceConfiguration> */
protected array $taskResourceConfigurations = [];
public static function make(): static
{
return app(static::class);
}
public function getId(): string
{
return 'tasks';
}
/**
* @param array<TaskResourceConfiguration> $configurations
*/
public function taskResources(array $configurations): static
{
$this->taskResourceConfigurations = $configurations;
return $this;
}
public function register(Panel $panel): void
{
$panel->resources([
TaskResource::class,
...$this->taskResourceConfigurations,
]);
}
public function boot(Panel $panel): void
{
//
}
}插件用户随后可以注册多个任务视图:
use Vendor\TasksPlugin\TasksPlugin;
use Vendor\TasksPlugin\TaskResource;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(
TasksPlugin::make()
->taskResources([
TaskResource::make('my-tasks')
->ownedByCurrentUser(),
TaskResource::make('team-tasks')
->ownedByCurrentTeam(),
])
);
}