Laravel Jetstream:使用 Spatie 权限添加 CRUD
发布于 作者 PovilasKorop
Laravel Jetstream 是一个入门套件,它不仅可以帮助您进行身份验证脚手架,还可以提供团队或双因素身份验证等额外功能。但我发现许多人难以在安装后自定义 Jetstream,以及添加更多功能。因此,在这篇文章中,让我们在 Jetstream 的基础上添加一个简单的 CRUD 和角色/权限。
Jetstream 安装
我们将创建一个新的 Laravel 项目来管理待办事项列表。安装 Laravel + Jetstream 有多种方法,我将使用 Laravel 安装程序。
您可以执行以下操作
laravel new projectcd projectcomposer require laravel/jetstreamphp artisan jetstream:install livewire
**注意**:Jetstream 提供了两种选项 - Livewire 和 Inertia。在本文中,我将使用 Livewire 堆栈,但这并不重要。Livewire/Inertia 用于脚手架代码,但在安装 Jetstream 后,您可以使用纯 Laravel + Blade MVC 代码,无需任何这些额外的工具继续编写代码。
因此,安装说明如上所示,但这里我将给您一个 **简短** 安装的提示。Laravel 安装程序允许您在一行命令中完成所有操作
laravel new project --jet --stack=livewire
脚手架完成后,我们需要运行以下命令
cd projectphp artisan migratenpm install && npm run dev
如果一切顺利,我们应该看到默认的主页,在右上角有登录/注册链接。
当您单击注册时,您应该会进入注册表单。
填写完后,用户将注册并进入仪表板。
如果您在屏幕上看到相同的内容,恭喜您,安装部分已成功完成。
待办事项列表:数据库结构和非 Jetstream 部分
接下来,我们需要为待办事项数据库表创建后端结构。在这里,我们将暂时离开 Jetstream。这是开发人员应该了解 Jetstream 的主要内容之一:您不必依赖入门套件来编写后续代码。您可以像往常一样在没有 Jetstream 的情况下编写代码,可能只是重用其中的一些组件。
因此,此时我们需要做的是
- 创建一个 Task 模型
- 创建数据库迁移
- 创建一个 Resource TaskController 并填充 CRUD 操作
- 向该新控制器添加路由
- 创建 Form Request 类以进行验证
这些是与入门套件无关的操作,无论它是 Jetstream、Breeze 还是其他任何套件。
然后,我们需要将以下操作整合到 Jetstream 中
- 将“任务”菜单项添加到 Jetstream 导航
- 创建 CRUD 操作的 Blade 视图
因此,这两件事将取决于 Jetstream 的前端结构,我们将在下面看一下。
首先,非 Jetstream 部分
php artisan make:model Task -mcrR
这些额外的标志将生成相关文件
-
-m
将生成一个迁移类 -
-cr
将生成一个包含 Resource 方法的控制器:index、create、store、show、edit、update、destroy -
-R
将生成两个 Form Request 类:StoreTaskRequest 和 UpdateTaskRequest
接下来,我们用一个字段填充迁移,以实现此简单示例。
database/migrations/2022_04_19_131435_create_tasks_table.php:
return new class extends Migration{ public function up() { Schema::create('tasks', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); }); }
我故意没有显示 down()
方法,因为自从 2017 年 Taylor Otwell 批准它 后,我就没有创建该方法。
我们也运行 php artisan migrate
,这样此表就会被创建。
然后,在 Model 中,我添加一个可填充字段
app/Models/Task.php
class Task extends Model{ protected $fillable = ['name'];}
然后,是控制器的 CRUD 代码。
app/Http/Controllers/TaskController.php:
use App\Http\Requests\StoreTaskRequest;use App\Http\Requests\UpdateTaskRequest;use App\Models\Task; class TaskController extends Controller{ public function index() { $tasks = Task::all(); return view('tasks.index', compact('tasks')); } public function create() { return view('tasks.create'); } public function store(StoreTaskRequest $request) { Task::create($request->validated()); return redirect()->route('tasks.index'); } public function edit(Task $task) { return view('tasks.edit', compact('task')); } public function update(UpdateTaskRequest $request, Task $task) { $task->update($request->validated()); return redirect()->route('tasks.index'); } public function destroy(Task $task) { $task->delete(); return redirect()->route('tasks.index'); }}
最后,我们使用 auth
中间件将该控制器分配给路由。
routes/web.php:
Route::get('/', function () { return view('welcome');}); Route::middleware([ 'auth:sanctum', config('jetstream.auth_session'), 'verified'])->group(function () { Route::get('/dashboard', function () { return view('dashboard'); })->name('dashboard'); // This is our new line Route::resource('tasks', \App\Http\Controllers\TaskController::class);});
这是我们第一次看到 Jetstream 中的内容:生成的仪表板路由,其中包含默认的中间件,例如 auth:sanctum
等。我们的任务只是将我们的路由添加到该组中。
待办事项列表:使用 Jetstream 布局的新页面
好的,我们现在有了控制器,但还没有视图。我们指定它为 resources/views/tasks/index.blade.php
,所以让我们创建该确切的文件。
如果您希望它具有相同的 Jetstream 设计,您可以使用现有的仪表板文件,只需替换其中的部分。
resources/views/dashboard.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Dashboard') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg"> <x-jet-welcome /> </div> </div> </div></x-app-layout>
因此,我们在 IDE 中执行“文件” ->“另存为...”操作,将其保存为 resources/views/tasks/index.blade.php
,然后用“任务列表”替换标题,用静态文本“即将推出”替换 <x-jet-welcome />
,暂时就这样。
resources/views/tasks/index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Tasks list') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg"> Coming soon. </div> </div> </div></x-app-layout>
现在,如果我们在浏览器中启动 /tasks
URL,我们应该看到以下内容
如果您不熟悉 x-app-layout
或 x-slot
之类的语法,请阅读有关 使用 Blade 组件的布局 的内容。
如果您不熟悉 __()
方法,请阅读有关 Laravel 中的翻译 的内容。
最后,我们在两个地方添加了顶部菜单,使用 Jetstream 组件 x-jet-nav-link
和 x-jet-responsive-nav-link
。我们只需复制粘贴仪表板链接并更改文本/路由。
resources/views/navigation-menu.blade.php:
<x-jet-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')"> {{ __('Dashboard') }}</x-jet-nav-link><x-jet-nav-link href="{{ route('tasks.index') }}" :active="request()->routeIs('tasks.*')"> {{ __('Tasks') }}</x-jet-nav-link> <x-jet-responsive-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')"> {{ __('Dashboard') }}</x-jet-responsive-nav-link><x-jet-responsive-nav-link href="{{ route('tasks.index') }}" :active="request()->routeIs('tasks.*')"> {{ __('Tasks') }}</x-jet-responsive-nav-link>
待办事项列表:使用 Tailwind CSS 的 Jetstream 表格
现在,让我们用实际的表格替换“即将推出”文本。
Laravel Jetstream 的视觉设计基于 Tailwind CSS 框架,因此我们应该继续使用它来创建其他自定义页面。
您可以从各种来源获取基于 Tailwind 的表格组件,我在这里选择了 Flowbite 的免费版本。因此,我将从那里复制粘贴代码,并使用任务列表的 @forelse
循环填充详细信息。
resources/views/tasks/index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Tasks list') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg"> <div class="relative overflow-x-auto shadow-md sm:rounded-lg"> <table class="w-full text-sm text-left text-gray-500 dark:text-gray-400"> <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> <tr> <th scope="col" class="px-6 py-3"> Task name </th> <th scope="col" class="px-6 py-3"> </th> </tr> </thead> <tbody> @forelse ($tasks as $task) <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> <td class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"> {{ $task->name }} </td> <td class="px-6 py-4"> <a href="{{ route('tasks.edit', $task) }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Edit</a> </td> </tr> @empty <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> <td colspan="2" class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"> {{ __('No tasks found') }} </td> </tr> @endforelse </tbody> </table> </div> </div> </div> </div></x-app-layout>
如果我在数据库中手动创建一些随机任务,它应该看起来像这样
完整 CRUD:按钮和表单
在表格上方,让我们放置一个按钮来添加新记录。Jetstream 有一组用于各种 UI 元素的 Blade 组件,包括按钮。要发布它们,我们需要运行以下命令
php artisan vendor:publish --tag=jetstream-views
然后,我们将在 resources/views/vendor/jetstream/components
中看到很多元素,并且它们会通过 Jetstream 的服务提供者自动启用。
如果我们打开 resources/views/vendor/jetstream/components/button.blade.php
,我们会看到以下内容
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center ...']) }}> {{ $slot }}</button>
在 items-center
后面列出了很多其他 Tailwind 类,这正是我们需要的,但只是链接的形式,而不是按钮。因此,我只需将所有这些类复制粘贴到我将创建的新组件中
resources/views/components/link.blade.php:
<a {{ $attributes->merge(['class' => 'inline-flex items-center ...']) }}> {{ $slot }}</a>
然后,在 resources/views/tasks/index.blade
中,我们可以执行以下操作
<div class="relative ..."> <x-link href="{{ route('tasks.create') }}" class="m-4">Add new task</x-link> <table class="...">
因此,我们添加了 <x-link>
,将其样式设置为按钮,并添加了 m-4
类以设置边距。
请注意,您需要再次运行 npm run dev
,或者在后台运行 npm run watch
,以便 Tailwind 重新编译在 Blade 文件中使用的所有类,例如我们示例中的 m-4
。
这是视觉结果
当我们单击该链接时,我们会进入 /tasks/create
URL,它对应于控制器方法 TaskController@create
,并加载 tasks/create.blade.php
的视图。
对于该表单,我将重用 Jetstream 中注册页面中的 HTML 代码和类,该页面位于 resources/views/auth/register.blade.php
中。复制粘贴后,以下是我们新创建的文件中的新表单。
resources/views/tasks/create.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Add New Task') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg"> <div class="relative overflow-x-auto shadow-md sm:rounded-lg px-4 py-4"> <x-jet-validation-errors class="mb-4" /> <form method="POST" action="{{ route('tasks.store') }}"> @csrf <div> <x-jet-label for="name" value="{{ __('Name') }}" /> <x-jet-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" /> </div> <div class="flex mt-4"> <x-jet-button> {{ __('Save Task') }} </x-jet-button> </div> </div> </div> </div> </div></x-app-layout>
如您所见,我们使用了相同的布局,只是添加了一些用于填充的 px-4 py-4
CSS 类。
另外,要注意两个 Jetstream 组件
-
x-jet-validation-errors
将列出来自会话的所有表单验证错误 -
x-jet-button
与您之前见过的提交按钮相同,作为我们x-link
组件的“灵感来源”
视觉效果如下
保存数据应该可以正常工作,因为我们已经为此创建了控制器代码,还记得吗?只需要添加验证规则。
app/Http/Requests/StoreTaskRequest.php:
class StoreTaskRequest extends FormRequest{ public function authorize() { // default is "false", we need to change to "true" return true; } public function rules() { return [ 'name' => 'required' ]; }}
我们还为更新表单生成了 Form Request 类,因此在 app/Http/Requests/UpdateTaskRequest.php
中,我们需要添加相同代码的 authorize()
和 rules()
方法。是否使用单独的 Form Request 类值得商榷,尤其是如果规则相同的情况下,但我个人更倾向于避免使用相同的类,因为你永远不知道规则什么时候会开始不同。
现在,编辑表单几乎与创建表单完全相同。因此,我们可以打开 create.blade.php
,执行 File -> Save as...
并保存它,只做少量更改
resources/views/tasks/edit.blade.php
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Edit Task') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg"> <div class="relative overflow-x-auto shadow-md sm:rounded-lg px-4 py-4"> <x-jet-validation-errors class="mb-4" /> <form method="POST" action="{{ route('tasks.update', $task) }}"> @csrf @method('PUT') <div> <x-jet-label for="name" value="{{ __('Name') }}" /> <x-jet-input id="name" class="block mt-1 w-full" type="text" name="name" :value="$task->name" required autofocus autocomplete="name" /> </div> <div class="flex mt-4"> <x-jet-button> {{ __('Save Task') }} </x-jet-button> </div> </div> </div> </div> </div></x-app-layout>
最后,我们需要构建删除按钮。我们将使用一个新的 Jetstream 组件,名为 x-jet-danger-button
。同时,让我们将“编辑”链接替换为更像按钮的样子。
resources/views/tasks/index.blade.php:
<td class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"> {{ $task->name }}</td><td class="px-6 py-4"> <x-link href="{{ route('tasks.edit', $task) }}">Edit</x-link> <form method="POST" action="{{ route('tasks.destroy', $task) }}" class="inline-block"> @csrf @method('DELETE') <x-jet-danger-button type="submit" onclick="return confirm('Are you sure?')">Delete</x-jet-danger-button> </form></td>
视觉效果
角色和权限
对于这部分,我们将使用一个广为人知的流行包,名为 Laravel Permission,由 Spatie 开发。
正如您将看到的,其用法独立于 Jetstream,与您在任何其他 Laravel 项目中使用它一样。换句话说,Jetstream 为您提供了带有登录/注册表单和配置文件管理的基本身份验证,但您在上面添加的任何内容,大多数情况下都不会“关心”Jetstream。
为了举一个简单的例子,让我们允许任何用户查看任务,但只有管理员用户可以添加/编辑/删除任务。
因此,正如 该包的文档 中所述,我们运行
composer require spatie/laravel-permissionphp artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"php artisan migrate
然后,我们需要在 User 模型中启用权限。
app/Models/User.php:
use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable{ use HasRoles;
默认情况下,任何注册用户都没有任何角色或权限。我们将创建一个单独的 seeder 类来创建具有权限的管理员。
php artisan make:seeder AdminUserSeeder
database/seeders/AdminUserSeeder.php:
use App\Models\User;use Illuminate\Database\Seeder;use Spatie\Permission\Models\Permission;use Spatie\Permission\Models\Role; class AdminUserSeeder extends Seeder{ public function run() { $adminRole = Role::create(['name' => 'Administrator']); $permission = Permission::create(['name' => 'manage tasks']); $permission->assignRole($adminRole); $adminUser = User::factory()->create([ 'password' => bcrypt('SecurePassword') ]); $adminUser->assignRole('Administrator'); }}
现在,我们需要检查谁有权限管理任务。您可以通过权限名称或角色名称进行检查,这取决于您的个人喜好。
首先,在 Blade 文件中,查看三个 @can ... @endcan
块。
resources/views/tasks/index.blade.php:
@can('manage tasks') <x-link href="{{ route('tasks.create') }}" class="m-4">Add new task</x-link>@endcan<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400"> <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> <tr> <th scope="col" class="px-6 py-3"> Task name </th> @can('manage tasks') <th scope="col" class="px-6 py-3"> </th> @endcan </tr> </thead> <tbody> @forelse ($tasks as $task) <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> <td class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"> {{ $task->name }} </td> @can('manage tasks') <td class="px-6 py-4"> <x-link href="{{ route('tasks.edit', $task) }}">Edit</x-link> <form method="POST" action="{{ route('tasks.destroy', $task) }}" class="inline-block"> @csrf @method('DELETE') <x-jet-danger-button type="submit" onclick="return confirm('Are you sure?')">Delete</x-jet-danger-button> </form> </td> @endcan </tr> @empty <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> <td colspan="2" class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"> {{ __('No tasks found') }} </td> </tr> @endforelse </tbody></table>
然后,我们还需要保护后端,因此请查看控制器中的 $this->authorize()
检查。
app/Http/Controllers/TaskController.php:
class TaskController extends Controller{ public function index() { $tasks = Task::all(); return view('tasks.index', compact('tasks')); } public function create() { $this->authorize('manage tasks'); return view('tasks.create'); } public function store(StoreTaskRequest $request) { $this->authorize('manage tasks'); Task::create($request->validated()); return redirect()->route('tasks.index'); } public function edit(Task $task) { $this->authorize('manage tasks'); return view('tasks.edit', compact('task')); } public function update(UpdateTaskRequest $request, Task $task) { $this->authorize('manage tasks'); $task->update($request->validated()); return redirect()->route('tasks.index'); } public function destroy(Task $task) { $this->authorize('manage tasks'); $task->delete(); return redirect()->route('tasks.index'); }}
结论
就这样,我们在 Jetstream 之上构建了我们的 CRUD,并添加了角色/权限。我的总体目标是向您展示 Jetstream 只是一个入门套件,但之后您可以编写任何您想要的自定义代码,大多时候可以忽略 Jetstream。
也就是说,有一些有用的 Blade 组件可以重复使用,用于 UI 元素,如按钮、链接等。
您可以在 官方文档 中阅读有关 Jetstream 的更多信息。
此演示项目的代码库 在此公开。