领略 Laravel 和 Filament 后台的魔力
本教程将通过创建一个后台面板的过程,带你一起领略 Laravel 的魔力,了解 Filament 后台项目。
如果你此前没接触过 Filament,就以此来作为第一个项目吧。同时,我也会为刚刚刚接触 Laravel 和 Livewire 的信任尽量提供一些有用信息。
我们就先从复制,粘贴开始,尽快完成一个作品吧。
PHP 工具
Laravel
Laravel 是用于创建网页应用的 PHP 框架。像其他框架一样,Laravel 刚开始也有一个陡峭的学习曲线,不过一旦你入门了,你很快就能创建一个网页应用。
Livewire
Livewire 是一个让前端界面和后端代码贴合的粘合剂。你可以借此创建响应式界面,不用刷新页面就能处理数据。你只需使用你学过的 Laravel 知识就可以使用它。
Filament Admin
Filament 就像炼金术士,将以上两个元素组合,使开发者可以花费更少的时间编写后台面板。你可能会因为发现第一天居然就能做这么多东西而万分吃惊。
准备一个空的项目框架
全新安装 Laravel
首先是从无到有创建一个全新的 Laravel 项目。你可以自己选择项目名,这里使用的是 magic-admin
composer create-project laravel/laravel magic-admin
cd magic-admin
该命令会下载最新版的 Laravel。
文档: 第一个 Laravel 项目
Laravel Breeze 用于用户认证
使用以下命令添加完整的用户认证系统:
composer require laravel/breeze --dev
注意: --dev 标志只会在开发模式下安装这个包。难道生产环境中就不需要用户认证吗?不是的。当运行以下命令安装这个包后,包里的文件都会复制到应用下面:
php artisan breeze:install
请注意,如果刚接触 Laravel: artisan 命令是 Laravel 带来一个魔法。你可以运行 php artisan list
命令查看它能做的所有事情。
Breeze 将提供完整的用户认证,也包括注册和登录表单、退出和密码重置处理。1 分钟完成!
安装 Filament 后台
接下来安装 Filament 后台:
composer require filament/filament
数据库安装
无论你使用的是什么样的环境,你需要为应用准备一个数据库连接。你可以在你的 Laravel 项目的根目录的 .env
文件中找到这些设置:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
迁移文件包含了创建数据库的指令。migrate 命令用来初始化数据库,创建像 users、password_reset 这样的表格等:
php artisan migrate
不生效?如果命令报错,请再检查以下 .env
文件下的数据库连接配置。
文档: 执行迁移
编译 Javascript 和 CSS
在浏览器中加载网站前,需要先将使用的 CSS 和 Javascript 进行编译。本教程将使用默认配置,此命令将会生成 app.css
和 app.js
文件:
npm install
npm run dev
文档: Laravel Mix
新项目状态检查
安装了 Laravel, Breeze 和 Filament 之后,我们便有了真实网页应用的骨架。
- Laravel 标准的欢迎页面位于域名根目录 /
- Breeze 认证页面在 /login 和 /register 下(其他还有 /dashboard )
- Filament 将自己放在需要登录的 /admin 之后
- 一旦注册登录,你就可以在 /admin 看到后台面板
Filament 的魔力
第一次使用 Filament 创建后台的时候,我是真的惊呆了。先添加这个包到你的项目中:
composer require doctrine/dbal
这能让 Filament 读取你的数据库结构。
现在,我们开始施法吧:
php artisan make:filament-resource User --generate
这一 artisan 命令会基于 Breeze 创建的 User
模型生成 Filament Resource 代码。-- generate
标志会读取现有的 users 表格在后台资源中自动生成匹配的字段。
功能齐全!小手一挥,你就可以创建、读取、更新和删除用户了。
现在想象一下,像这样为整个应用自动生成资源。你可以在几分钟内完成后台面板 80% 的工作量。
之所以说是 80%,因为你还需要花点时间去修补和调整自动生成的资源。这是本教程余下的时间要做的事情。涉及的魔法会随着我们的深入而减少,不过仍然有一些好玩的小诀窍。
生成假用户
我们来使用 Laravel 模型工厂为 CRUD 创建一些数据吧。这一假用户生成器也会在安装 Breeze 的时候为我们创建好。
首先,进入 tinker 模式,在这里可以快速运行 PHP 代码:
php artisan tinker
这里产生一个新命令窗口,你可以输入以下命令生成 20 个随机用户:
User::factory()->count(20)->create();
你可以输入 quit
退出 tinker 命令行。
自定义Filament资源
自动生成的 Filament 资源 有两个主要的组件:table
定义了 用户展示列表,form
定义了如何创建和编辑用户。
两者都在 app/Filament/Resources/UserResource.php
文件中定义。
调整用户列表
以下就是自动生成的表格:
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name'),
Tables\Columns\TextColumn::make('email'),
Tables\Columns\TextColumn::make('email_verified_at')
->dateTime(),
Tables\Columns\TextColumn::make('created_at')
->dateTime(),
Tables\Columns\TextColumn::make('updated_at')
->dateTime(),
])
->filters([
//
]);
}
让列表可搜索
你看多简单!只要在你要搜索的字段中添加 searchable()
。
Tables\Columns\TextColumn::make('name')->searchable(),
Tables\Columns\TextColumn::make('email')->searchable(),
刷新页面后搜索框会出现在表格的顶部。很神奇吧!
修改 email 验证为布尔值
我不关心什么时候验证,不过想知道是否已经验证了。我们可以将 TextColumn
改为 BooleanColumn
。
// 修改前
Tables\Columns\TextColumn::make('email_verified_at')->dateTime(),
// 修改后
Tables\Columns\BooleanColumn::make('email_verified_at'),
它会把所有日期作为 true
在表格中显示为绿色的勾号。
文档: Boolean 字段
修改字段标签
Email verified at 标签不再适用。我们可以设置一个自定义标签:
Tables\Columns\BooleanColumn::make('email_verified_at')->label('Verified'),
文档: 设置标签
添加过滤器 Filter
我们可以为表格添加过滤器,来筛选展示的记录。接下来,我们添加一个复选框用户过滤显示验证过的用户。
// 在 table 方法中找到空的 filters 方法
->filters([
//
]);
// 你需要在顶部引入 Builder 类
use Illuminate\Database\Eloquent\Builder;
// 然后就可以添加 filter 了
->filters([
Tables\Filters\Filter::make('verified')
->query(fn (Builder $query): Builder => $query->whereNotNull('email_verified_at')),
]);
刷新后你会在搜索框旁边看到一个小的烟囱图标。点击就可以选择只显示验证用户了。
那么再添加一个过滤器只显示未验证用户吧。
->filters([
Tables\Filters\Filter::make('verified')
->query(fn (Builder $query): Builder => $query->whereNotNull('email_verified_at')),
Tables\Filters\Filter::make('unverified')
->query(fn (Builder $query): Builder => $query->whereNull('email_verified_at')),
]);
文档: 表格过滤器 Filters
修改显示日期
默认 datetime 字段会显示像 Dec 27, 2021 23:08:03 这样的日期。我们可以用标准的 PHP 日期格式将其改成可读性更强的格式。
// 此处将显示'Dec 21, 2021',而不显示时间
Tables\Columns\TextColumn::make('created_at')
->dateTime('M j, Y'),
Tables\Columns\TextColumn::make('updated_at')
->dateTime('M j, Y'),
文档: Text Column 格式化
根据日期排序
如果我们从新到旧对日期进行排序会很方便。使用 sortable()
Tables\Columns\TextColumn::make('created_at')
->dateTime('M j, Y')
->sortable(),
Tables\Columns\TextColumn::make('updated_at')
->dateTime('M j, Y')
->sortable(),
现在字段名变成可点击的了。再次点击会换方向排序。请注意,随机生成的用户会再同一天创建,因此日期看起来是一样的。
文档: 字段排序
修改用户创建/编辑表单
自动生成的表单让我们可以开箱即用修改用户 name、email、password 字段。
表单字段同样可以在 app/Filament/Resources/UserResource.php
文件中找到。
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('email')
->email()
->required()
->maxLength(255),
Forms\Components\TextInput::make('password')
->password()
->required()
->maxLength(255),
]);
}
解决密码 Hash 问题
这个用户表单中包含了一个密码字段。不过,如果你输入一个新密码,它在数据库中会以明文保存。Laravel 哈希加密所有密码,因此尝试用未哈希的密码登录会登录失败。
注意: 我通常的方案是,在用户模型中创建一个
setPasswordAttribute()
修改器。这样密码一修改,就会被自动哈希。不过,这也要修改标准的认证过程,移除所有调用Hash::make()
的地方,否则会双重哈希。
我们来看看 Filament 视角下是如何修改的:
// 需要引入此类
use Illuminate\Support\Facades\Hash;
Forms\Components\TextInput::make('password')
->password()
->required()
->maxLength(255)
->dehydrateStateUsing(fn ($state) => Hash::make($state)),
上述 dehydrate 指向了 数据修改的 Livewire 生命周期。dehydrateStateUsing
函数会在输入改变时调用,对密码进行哈希加密。
创建新用户时这样没有问题,不过如果你更新现有用户时,没有修改密码,就会出现双重哈希。简单的方法是,让密码字段只在创建用户页面显示。
// 需要引入
use Livewire\Component;
Forms\Components\TextInput::make('password')
->password()
->required()
->maxLength(255)
->dehydrateStateUsing(fn ($state) => Hash::make($state))
->visible(fn (Component $livewire): bool => $livewire instanceof Pages\CreateUser),
文档: 基于页面隐藏组件
添加 email_verified_at 字段
这样就能手动验证用户。email_verified_at
字段要么是一个日期,要么是 null
。
你可以在表单后面插入此代码:
Forms\Components\DatePicker::make('email_verified_at'),
由于 email_verified_at
在数据库中是时间戳,我选择使用了 DatePicker
而非 DateTimePicker
。这样将时间设置简化为 00:00:00。
文档: 日期时间选择器
我们并需要将此属性添加到 app/Models/User.php
中的 $fillable
数组:
protected $fillable = [
'name',
'email',
'password',
'email_verified_at',
];
注意: 通常这一步是通过点击操作按钮调用
user()->markEmailAsVerified()
来实现,而非使用在表单中日期字段。不过,此处目的在于展示如何添加新的字段。
收尾
我们已经展示了 Laravel 和 Filament 的魔力,它们能让你用惊人的速度搭建后台面板。我们还能继续花费数小时做些微调和自定义,不过我们暂且先这样吧。接下来还有一些事情需要处理才能上线。
管理Filament导航菜单
当只有一个用户资源时可能不那么重要,不过随着项目的增长,你可能需要在导航菜单中重新组织一下这些资源。
在 app/Filament/Resources/UserResource.php
文件的顶部可对其进行设置:
class UserResource extends Resource
{
protected static ?string $model = User::class;
protected static ?string $navigationIcon = 'heroicon-o-collection';
...
}
我们为用户模型调整使用合适的图标。既然已涉及此,我们不妨也同时添加一个排序号,来定义该导航菜单的显示顺序:
// 修改此项
protected static ?string $navigationIcon = 'heroicon-o-users';
// 添加此项
protected static ?int $navigationSort = 1;
这样用户菜单就会排在前面。
文档: Filament 后台导航
后台管理面板安全
Laravel 环境设置的是 local 时,所有的用户都能访问后面面板。不过如果你上线到生产(production)环境,包括你自己在内的所有人都会被锁住。要让用户获得授权,需要修改 app/Models/User.php
文件下的用户模型。
- 在类声明中实现
FilamentUser
接口 - 引入
Filament\Models\Contracts\FilamentUser
- 添加
canAccessFilament()
方法
<?php
namespace App\Models;
use Filament\Models\Contracts\FilamentUser;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements FilamentUser
{
// ...
public function canAccessFilament(): bool
{
return $this->email == 'admin@laravel-filament.cn' && $this->hasVerifiedEmail();
}
}
最终结果
这样,我们就安装好了 Filament 后台面板,并自定义了一个资源来管理用户。使用其他模型重复这么一个过程,你就能完成一个功能齐全的后台面板了!
当你使用关联连接资源时,你也能发现一些很有趣的功能实现。不过,本教程会先止于此。
这里是修改后完整的 UserResource.php
文件。(未包含 app/Models/User.php
模型文件)。
<?php
namespace App\Filament\Resources;
use Closure;
use Filament\Forms;
use App\Models\User;
use Filament\Tables;
use Livewire\Component;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Illuminate\Support\Facades\Hash;
use Illuminate\Database\Eloquent\Builder;
use App\Filament\Resources\UserResource\Pages;
use App\Filament\Resources\UserResource\RelationManagers;
class UserResource extends Resource
{
protected static ?string $model = User::class;
protected static ?string $navigationIcon = 'heroicon-o-users';
protected static ?int $navigationSort = 1;
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('email')
->email()
->required()
->maxLength(255),
Forms\Components\TextInput::make('password')
->password()
->required()
->maxLength(255)
->dehydrateStateUsing(fn ($state) => Hash::make($state))
->visible(fn (Component $livewire): bool => $livewire instanceof Pages\CreateUser),
Forms\Components\DatePicker::make('email_verified_at'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')->searchable(),
Tables\Columns\TextColumn::make('email')->searchable(),
Tables\Columns\BooleanColumn::make('email_verified_at')->label('Verified'),
Tables\Columns\TextColumn::make('created_at')
->dateTime('M j, Y')
->sortable(),
Tables\Columns\TextColumn::make('updated_at')
->dateTime('M j, Y')
->sortable(),
])
->filters([
Tables\Filters\Filter::make('verified')
->query(fn (Builder $query): Builder => $query->whereNotNull('email_verified_at')),
Tables\Filters\Filter::make('unverified')
->query(fn (Builder $query): Builder => $query->whereNull('email_verified_at')),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListUsers::route('/'),
'create' => Pages\CreateUser::route('/create'),
'edit' => Pages\EditUser::route('/{record}/edit'),
];
}
}