列出所有用户
本节我们将从数据库取出所有用户数据,并在用户列表页面将所有用户进行展示,并在顶部导航添加访问入口。最后我们还会为 1 号加上管理员权限,让他可以删除其他的用户。
用户列表
根据我们前面使用 resource 方法生成的符合 RESTful 架构的路由可知,用户列表对应用户控制器的 index 动作,页面 URL 对应 /users。接下来我们将在用户控制器中加入 index 动作。并且因为用户列表的访问权限是公开的,所以我们还需要在 Auth 中间件 except 中新增 index 动作来允许游客访问。
app/Http/Controllers/UsersController.php
<?php
namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
public function __construct()
{
$this->middleware('auth', [
'except' => ['show', 'create', 'store', 'index']
]);
.
.
.
}
public function index()
{
$users = User::all();
return view('users.index', compact('users'));
}
.
.
.
}
可以看到,在 index 方法中,我们使用 Eloquent 用户模型将所有用户的数据一下子完全取出来了,这么做会影响应用的性能,后面我们再来对该代码进行优化,通过分页的方式来读取用户数据。在将用户数据取出之后,与 index 视图进行绑定,这样便可以在视图中使用 $users 来访问所有用户实例。
接下来让我们继续创建 index 视图,用于显示所有用户列表的信息。
resources/views/users/index.blade.php
@extends('layouts.default')
@section('title', '所有用户')
@section('content')
<div class="offset-md-2 col-md-8">
<h2 class="mb-4 text-center">所有用户</h2>
<div class="list-group list-group-flush">
@foreach ($users as $user)
<div class="list-group-item">
<img class="mr-3" src="{{ $user->gravatar() }}" alt="{{ $user->name }}" width=32>
<a href="{{ route('users.show', $user) }}">
{{ $user->name }}
</a>
</div>
@endforeach
</div>
</div>
@stop
我们使用 @foreach 的方法将所有用户的数据逐个输出,并在页面上显示他们的头像和用户名。
现在用户列表页已经可以访问了,接下来让我们对顶部导航进行编辑,为用户列表加上指定链接,方便用户跳转到用户列表页面进行查看。
resources/views/layouts/_header.blade.php
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container ">
<a class="navbar-brand" href="{{ route('home') }}">Weibo App</a>
<ul class="navbar-nav justify-content-end">
@if (Auth::check())
<li class="nav-item"><a class="nav-link" href="{{ route('users.index') }}">用户列表</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ Auth::user()->name }}
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('users.show', Auth::user()) }}">个人中心</a>
<a class="dropdown-item" href="{{ route('users.edit', Auth::user()) }}">编辑资料</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" id="logout" href="#">
<form action="{{ route('logout') }}" method="POST">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<button class="btn btn-block btn-danger" type="submit" name="button">退出</button>
</form>
</a>
</div>
</li>
@else
<li class="nav-item"><a class="nav-link" href="{{ route('help') }}">帮助</a></li>
<li class="nav-item" ><a class="nav-link" href="{{ route('login') }}">登录</a></li>
@endif
</ul>
</div>
</nav>
假数据的生成分为两个阶段:
对要生成假数据的模型指定字段进行赋值 - 『模型工厂』;
批量生成假数据模型 - 『数据填充』;
我们可以借助 Faker 和 Eloquent 模型工厂来为指定模型的每个字段设置随机值。
本项目中生成的模型工厂如下:
database/factories/UserFactory.php
<?php
use App\Models\User;
use Illuminate\Support\Str;
use Faker\Generator as Faker;
$factory->define(User::class, function (Faker $faker) {
$date_time = $faker->date . ' ' . $faker->time;
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
'remember_token' => Str::random(10),
'created_at' => $date_time,
'updated_at' => $date_time,
];
});
我们使用生成的假日期对用户的创建时间和更新时间进行赋值。
数据填充
在 Laravel 中我们使用 Seeder 类来给数据库填充测试数据。所有的 Seeder 类文件都放在 database/seeds 目录下,文件名需要按照『驼峰式』来命名,且严格遵守大小写规范。Laravel 默认为我们定义了一个 DatabaseSeeder 类,我们可以在该类中使用 call 方法来运行其它的 Seeder 类,以此控制数据填充的顺序。我们可以使用下面命令来生成一个 UsersTableSeeder 文件,用于填充用户相关的假数据。
php artisan make:seeder UsersTableSeeder
在我们定义好了用户模型工厂之后,便可以在生成的用户数据填充文件中使用 factory 这个辅助函数来生成一个使用假数据的用户对象。
现在让我们使用该方法来创建 50 个假用户。
database/seeds/UsersTableSeeder.php
<?php
use Illuminate\Database\Seeder;
use App\Models\User;
class UsersTableSeeder extends Seeder
{
public function run()
{
$users = factory(User::class)->times(50)->make();
User::insert($users->makeVisible(['password', 'remember_token'])->toArray());
$user = User::find(1);
$user->name = 'Summer';
$user->email = 'summer@example.com';
$user->save();
}
}
times 和 make 方法是由 FactoryBuilder 类 提供的 API。times 接受一个参数用于指定要创建的模型数量,make 方法调用后将为模型创建一个 集合。makeVisible 方法临时显示 User 模型里指定的隐藏属性 $hidden,接着我们使用了 insert 方法来将生成假用户列表数据批量插入到数据库中。最后我们还对第一位用户的信息进行了更新,方便后面我们使用此账号登录。
接着我们还需要在 DatabaseSeeder 中调用 call 方法来指定我们要运行假数据填充的文件。
database/seeds/DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
class DatabaseSeeder extends Seeder
{
public function run()
{
Model::unguard();
$this->call(UsersTableSeeder::class);
Model::reguard();
}
}
完成上面操作之后,我们便可以开始为用户生成批量假数据了,在运行生成假数据的命令之前,我们需要使用 migrate:refresh 命令来重置数据库,之后再使用 db:seed 执行数据填充。
$ php artisan migrate:refresh
$ php artisan db:seed
如果我们要单独指定执行 UserTableSeeder 数据库填充文件,则可以这么做:
$ php artisan migrate:refresh
$ php artisan db:seed --class=UsersTableSeeder
你也可以使用下面一条命令来同时完成数据库的重置和填充操作:
php artisan migrate:refresh --seed
分页
首先,我们需要先对用户控制器中获取所有用户数据的方法进行更改,修改如下。
app/Http/Controllers/UsersController.php
<?php
namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
.
.
.
public function index()
{
$users = User::paginate(10);
return view('users.index', compact('users'));
}
.
.
.
默认状况下,页面的当前页数由 HTTP 请求所带的 page 参数决定,当你访问 weibo.test/users?page=2 链接时,获取的是第二页的用户列表信息,Laravel 会自动检测到 page 的值并插入由分页器生成的链接中。在上面代码我们使用 paginate 方法来指定每页生成的数据数量为 10 条,即当我们有 50 个用户时,用户列表将被分为五页进行展示。
在调用 paginate 方法获取用户列表之后,便可以通过以下代码在用户列表页上渲染分页链接
{!! $users->render() !!}
由 render 方法生成的 HTML 代码默认会使用 Bootstrap 框架的样式,渲染出来的视图链接也都统一会带上 ?page 参数来设置指定页数的链接。另外还需要注意的一点是,渲染分页视图的代码必须使用 {!! !!} 语法,而不是 {{ }},这样生成 HTML 链接才不会被转义。
让我们对用户列表页视图进行修改,加上渲染分页视图的代码。
resources/views/users/index.blade.php
@extends('layouts.default')
@section('title', '所有用户')
@section('content')
<div class="offset-md-2 col-md-8">
<h2 class="mb-4 text-center">所有用户</h2>
<div class="list-group list-group-flush">
@foreach ($users as $user)
<div class="list-group-item">
<img class="mr-3" src="{{ $user->gravatar() }}" alt="{{ $user->name }}" width=32>
<a href="{{ route('users.show', $user) }}">
{{ $user->name }}
</a>
</div>
@endforeach
</div>
<div class="mt-3">
{!! $users->render() !!}
</div>
</div>
@stop
使用局部视图重构
为了对视图模块进行细分,使目录结构更好理解,接下来让我们对用户列表页进行重构,将单个用户视图抽离成一个完整的局部视图。首先我们引入用户局部视图到用户列表上
resources/views/users/index.blade.php
@extends('layouts.default')
@section('title', '所有用户')
@section('content')
<div class="offset-md-2 col-md-8">
<h2 class="mb-4 text-center">所有用户</h2>
<div class="list-group list-group-flush">
@foreach ($users as $user)
@include('users._user')
@endforeach
</div>
<div class="mt-3">
{!! $users->render() !!}
</div>
</div>
@stop
接着再对用户局部视图进行创建。
resources/views/users/_user.blade.php
<div class="list-group-item">
<img class="mr-3" src="{{ $user->gravatar() }}" alt="{{ $user->name }}" width=32>
<a href="{{ route('users.show', $user) }}">
{{ $user->name }}
</a>
</div>
Git 代码版本控制
接着让我们将本次更改纳入版本控制中:
$ git add -A
$ git commit -m "查看用户列表"
管理员
我们需要生成一个迁移文件来为用户表新增管理员字段。在生成迁移文件时,带上 --table 选项可以为指定数据表生成迁移文件。现在,让我们运行下面命令来为用户表新增管理员字段。
php artisan make:migration add_is_admin_to_users_table --table=users
我们需要在新建的迁移文件中为用户添加一个 is_admin 的布尔值类型字段来判别用户是否拥有管理员身份,该字段默认为 false,在迁移文件执行时对该字段进行创建,回滚时则需要对该字段进行移除。迁移文件最终编写完成的代码如下。
database/migrations/[timestamp]_add_is_admin_to_users_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddIsAdminToUsersTable extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_admin')->default(false);
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_admin');
});
}
}
可以看到我们使用了 dropColumn 方法来对指定字段进行移除。
在迁移文件创建成功之后,我们还需要运行数据库迁移。
php artisan migrate
现在应用中还不存在拥有管理员身份的用户,让我们对数据填充文件进行更改,将第一个生成的用户设置为管理员:
database/seeds/UsersTableSeeder.php
<?php
use Illuminate\Database\Seeder;
use App\Models\User;
class UsersTableSeeder extends Seeder
{
public function run()
{
$users = factory(User::class)->times(50)->make();
User::insert($users->makeVisible(['password', 'remember_token'])->toArray());
$user = User::find(1);
$user->name = 'Summer';
$user->email = 'summer@example.com';
$user->is_admin = true;
$user->save();
}
}
最后让我们对数据库进行重置和填充:
php artisan migrate:refresh --seed
现在如果我们使用 tinker 进行查看,可以看到第一位用户已被成功设置成为管理员。
$ php artisan tinker
App\Models\User::first()
=> App\Models\User {#2924
id: 1,
name: "Summer",
email: "summer@example.com",
email_verified_at: "2018-12-13 08:41:26",
created_at: "1994-05-03 16:35:37",
updated_at: "2018-12-13 08:41:26",
is_admin: 1,
}
由于创建日期和更新日期是随机生成的,因此你跟我显示的时间可能会有不同。
destroy 动作
删除用户的动作,有两个逻辑需要提前考虑:
只有当前登录用户为管理员才能执行删除操作;
删除的用户对象不是自己(即使是管理员也不能自己删自己)。
我们在开发更新用户功能时,已经创建了用户授权策略类,让我们接着对该授权策略类进行编辑,加上 destroy 删除用户动作相关的授权。
app/Policies/UserPolicy.php
<?php
namespace App\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
use App\Models\User;
class UserPolicy
{
use HandlesAuthorization;
public function update(User $currentUser, User $user)
{
return $currentUser->id === $user->id;
}
public function destroy(User $currentUser, User $user)
{
return $currentUser->is_admin && $currentUser->id !== $user->id;
}
}
我们使用下面这行代码来指明,只有当前用户拥有管理员权限且删除的用户不是自己时才显示链接。
$currentUser->is_admin && $currentUser->id !== $user->id;
Laravel 授权策略提供了 @can Blade 命令,允许我们在 Blade 模板中做授权判断。接下来让我们利用 @can 指令,在用户列表页加上只有管理员才能看到的删除用户按钮。
resources/views/users/_user.blade.php
<div class="list-group-item">
<img class="mr-3" src="{{ $user->gravatar() }}" alt="{{ $user->name }}" width=32>
<a href="{{ route('users.show', $user) }}">
{{ $user->name }}
</a>
@can('destroy', $user)
<form action="{{ route('users.destroy', $user->id) }}" method="post" class="float-right">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<button type="submit" class="btn btn-sm btn-danger delete-btn">删除</button>
</form>
@endcan
</div>
在管理员点击删除用户按钮之后,删除动作会映射到用户控制器的 destroy 动作上,接下来让我们为控制器添加基本的用户删除动作:
<?php
namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
.
.
.
public function destroy(User $user)
{
$user->delete();
session()->flash('success', '成功删除用户!');
return back();
}
}
在 destroy 动作中,我们首先会根据路由发送过来的用户 id 进行数据查找,查找到指定用户之后再调用 Eloquent 模型提供的 delete 方法对用户资源进行删除,成功删除后在页面顶部进行消息提示。最后将用户重定向到上一次进行删除操作的页面,即用户列表页。
有了上面的代码,管理员已经能够对用户进行删除操作了。并且我们使用了 Auth 中间件黑名单,也就是说除了 except 数组中指定的动作,其他的动作都必须登录以后才能操作:
app/Http/Controllers/UsersController.php
<?php
namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
public function __construct()
{
$this->middleware('auth', [
'except' => ['show', 'create', 'store', 'index']
]);
.
.
.
}
另外还需要注意的一点是,现在的删除动作是对所有登录用户开放的,为此我们还需要对删除动作加上授权策略,只允许已登录的 管理员 进行删除操作。
删除授权策略 destroy 我们已经在上面创建了,这里我们在用户控制器中使用 authorize 方法来对删除操作进行授权验证即可。在删除动作的授权中,我们规定只有当前用户为管理员,且被删除用户不是自己时,授权才能通过。
app/Http/Controllers/UsersController.php
<?php
namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
.
.
.
public function destroy(User $user)
{
$this->authorize('destroy', $user);
$user->delete();
session()->flash('success', '成功删除用户!');
return back();
}
}
至此,用户删除功能已经完成。
Git 代码版本控制#
接着让我们将本次更改纳入版本控制中:
$ git add -A
$ git commit -m "管理员可删除用户"
现在让我们将改动的代码进行提交并合并到主分支上。
$ git checkout master
$ git merge user-crud
//-------------下面是关于 destroy 的简要描述-----------
1 创建授权策略
2 加上 destroy 删除用户动作相关的授权。
app/Policies/UserPolicy.php
public function destroy(User $currentUser, User $user)
{
return $currentUser->is_admin && $currentUser->id !== $user->id;
}
3 @can 在 Blade 模板中做授权判断
@can('destroy', $user)
<form action="{{ route('users.destroy', $user->id) }}" method="post" class="float-right">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<button type="submit" class="btn btn-sm btn-danger delete-btn">删除</button>
</form>
@endcan
4
删除授权策略 destroy 我们已经在上面创建了,这里我们在用户控制器中使用 authorize 方法来对删除操作进行授权验证即可。在删除动作的授权中,我们规定只有当前用户为管理员,且被删除用户不是自己时,授权才能通过。
app/Http/Controllers/UsersController.php
public function destroy(User $user)
{
$this->authorize('destroy', $user);
$user->delete();
session()->flash('success', '成功删除用户!');
return back();
}
本文介绍了如何在 Laravel 项目中实现用户列表展示、权限控制,包括如何添加管理员权限、限制删除操作、分页显示和使用数据填充生成假数据。详细讲解了如何使用 Eloquent 模型、Auth 中间件、分页组件和数据管理策略。
563





