Filament 默认登录页面是使用 email 和 password 两个字段校验用户身份的,但是有的时候我们可能需要修改登录字段,比如使用 username 和 password 的组合,而不是 email 字段。在 Filament 3 中实现这一需求十分简单,通过自定义身份验证功能自定义一个登录类并重写相关方法即可。

创建自定义登录类

让我们创建自己的登录页面,并将其注入登录方法中。我将在 AppFilamentAuth 目录中创建这个类。它需要扩展 Filament 中的默认 Login 类。

use FilamentPagesAuthLogin as BaseLogin;
 
class Login extends BaseLogin
{
    // ...
}

Filament默认的 Login 类的完整代码参考 github。为了实现上文中的需求,我们需要重写三个方法:

  • form() – 以不同方式显示表单
  • getCredentialsFromFormData() – 正确处理表单数据
  • throwFailureValidationException() – 正确显示错误

此外,我们将用自己的新方法 getUsernameFormComponent() 替换 getEmailFormComponent() 方法,以使用 username 字段代替 email 字段,我们将把该字段称为 “用户名”。

自定义 getUsernameFormComponent 方法

protected function getUsernameFormComponent(): Component
{
    return TextInput::make('username')
        ->label('用户名')
        ->required()
        ->autocomplete()
        ->autofocus()
        ->extraInputAttributes(['tabindex' => 1]);
}

上述代码中,我们自定义了一个getUsernameFormComponent()方法,包含一个TextInput类型的表单字段。

重写 form 方法

public function form(Form $form): Form
{
    return $form
        ->schema([
            //$this->getEmailFormComponent(), 
            $this->getUsernameFormComponent(),
            $this->getPasswordFormComponent(),
            $this->getRememberFormComponent(),
        ])
        ->statePath('data');
}

通过覆盖 form 方法,使用 getUsernameFormComponent() 替换默认的 $this->getEmailFormComponent(),这样就实现了使用 username 字段替换 email 字段。

重写 getCredentialsFromFormData() 方法

接下来我们要重写 getCredentialsFromFormData() 方法,使用 username 进行身份验证。

protected function getCredentialsFromFormData(array $data): array
{
    return [
        'username' => $data['username'],
        'password' => $data['password'],
    ];
}

重写 throwFailureValidationException() 方法

现在的问题是验证错误仍然发生在 data.email 字段,而不是 data.username 字段,这意味着错误信息将不可见。我们需要通过覆盖throwFailureValidationException()方法来解决这个问题:

protected function throwFailureValidationException(): never
{
    throw ValidationException::withMessages([
        'data.username' => __('filament-panels::pages/auth/login.messages.failed'),
    ]);
}

完整的自定义 login 类代码

经过上面的步骤,我们已经按照需求完成了自定义 login 类的所有代码编写,完整代码如下:

<?php

namespace AppFilamentAuth;

use FilamentFormsForm;
use FilamentFormsComponentsTextInput;
use FilamentFormsComponentsComponent;
use FilamentPagesAuthLogin as BaseLogin;
use IlluminateValidationValidationException;

class Login extends BaseLogin
{
    public function form(Form $form): Form
    {
        return $form
            ->schema([
                //$this->getEmailFormComponent(),
                $this->getUsernameFormComponent(),
                $this->getPasswordFormComponent(),
                $this->getRememberFormComponent(),
            ])
            ->statePath('data');
    }

    protected function getUsernameFormComponent(): Component
    {
        return TextInput::make('username')
            ->label('用户名')
            ->required()
            ->autocomplete()
            ->autofocus()
            ->extraInputAttributes(['tabindex' => 1]);
    }

    protected function getCredentialsFromFormData(array $data): array
    {
        return [
            'username' => $data['username'],
            'password'  => $data['password'],
        ];
    }

    protected function throwFailureValidationException(): never
    {
        throw ValidationException::withMessages([
            'data.username' => __('filament-panels::pages/auth/login.messages.failed'),
        ]);
    }
}

应用自定义 login

最后,我们要修改 app/Providers/Filament/AdminPanelProvider.php 文件,在 login 中调用自定义的 login 类即可。

use AppFilamentAuthLogin;
 
class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->default()
            ->id('admin')
            ->path('admin')
            ->login(Login::class) 
            // ...
    }
}