跟社区学laravel博客实战6(下)模型工厂、数据填充

本文介绍了如何在 Laravel 项目中实现用户列表展示、权限控制,包括如何添加管理员权限、限制删除操作、分页显示和使用数据填充生成假数据。详细讲解了如何使用 Eloquent 模型、Auth 中间件、分页组件和数据管理策略。
摘要由CSDN通过智能技术生成

列出所有用户

本节我们将从数据库取出所有用户数据,并在用户列表页面将所有用户进行展示,并在顶部导航添加访问入口。最后我们还会为 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();
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值