Skip to content
虚位以待
虚位以待
赞助商
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

中级任务列表

介绍

本快速入门指南提供了对 Laravel 框架的中级介绍,包括数据库迁移、Eloquent ORM、路由、认证、授权、依赖注入、验证、视图和 Blade 模板的内容。如果您已经熟悉 Laravel 框架或 PHP 框架的基础知识,这是一个很好的起点。

为了体验 Laravel 的基本功能,我们将构建一个任务列表,用于跟踪我们想要完成的所有任务(典型的“待办事项”示例)。与“基础”快速入门不同,本教程将允许用户创建账户并通过应用程序进行认证。该项目的完整源代码可在 GitHub 上获取。

安装

当然,首先您需要一个全新的 Laravel 框架安装。您可以使用 Homestead 虚拟机 或您选择的本地 PHP 环境来运行框架。一旦您的本地环境准备就绪,您可以使用 Composer 安装 Laravel 框架:

php
composer create-project laravel/laravel quickstart --prefer-dist

您可以选择仅阅读本快速入门的其余部分;但是,如果您想下载本快速入门的源代码并在本地计算机上运行它,您可以克隆其 Git 仓库并安装其依赖项:

php
git clone https://github.com/laravel/quickstart-intermediate quickstart
cd quickstart
composer install
php artisan migrate

有关构建本地 Laravel 开发环境的更完整文档,请查看完整的 Homestead安装 文档。

准备数据库

数据库迁移

首先,让我们使用迁移来定义一个数据库表来保存我们所有的任务。Laravel 的数据库迁移提供了一种使用流畅、富有表现力的 PHP 代码定义数据库表结构和修改的简便方法。与其告诉您的团队成员手动向他们的本地数据库副本添加列,不如让您的队友简单地运行您推送到源代码控制的迁移。

users

由于我们将允许用户在应用程序中创建他们的账户,因此我们需要一个表来存储我们所有的用户。幸运的是,Laravel 已经附带了一个迁移来创建一个基本的 users 表,因此我们不需要手动生成一个。users 表的默认迁移位于 database/migrations 目录中。

tasks

接下来,让我们构建一个数据库表来保存我们所有的任务。Artisan CLI 可用于生成各种类,并将在您构建 Laravel 项目时为您节省大量输入。在这种情况下,让我们使用 make:migration 命令为我们的 tasks 表生成一个新的数据库迁移:

php
php artisan make:migration create_tasks_table --create=tasks

迁移将被放置在项目的 database/migrations 目录中。正如您可能注意到的,make:migration 命令已经向迁移文件添加了一个自动递增的 ID 和时间戳。让我们编辑此文件并为任务名称添加一个额外的 string 列,以及一个 user_id 列,该列将链接我们的 tasksusers 表:

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTasksTable extends Migration
{
    /**
     * 运行迁移。
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->index();
            $table->string('name');
            $table->timestamps();
        });
    }

    /**
     * 反转迁移。
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('tasks');
    }
}

要运行我们的迁移,我们将使用 migrate Artisan 命令。如果您使用的是 Homestead,您应该从虚拟机中运行此命令,因为您的主机计算机将无法直接访问数据库:

php
php artisan migrate

此命令将创建我们所有的数据库表。如果您使用您选择的数据库客户端检查数据库表,您应该会看到新的 tasksusers 表,其中包含我们在迁移中定义的列。接下来,我们准备定义我们的 Eloquent ORM 模型!

Eloquent 模型

Eloquent 是 Laravel 的默认 ORM(对象关系映射器)。Eloquent 使得使用清晰定义的“模型”在数据库中检索和存储数据变得轻而易举。通常,每个 Eloquent 模型直接对应一个数据库表。

User 模型

首先,我们需要一个与我们的 users 数据库表对应的模型。但是,如果您查看项目的 app 目录,您会看到 Laravel 已经附带了一个 User 模型,因此我们不需要手动生成一个。

Task 模型

因此,让我们定义一个与我们刚刚创建的 tasks 数据库表对应的 Task 模型。同样,我们可以使用 Artisan 命令生成此模型。在这种情况下,我们将使用 make:model 命令:

php
php artisan make:model Task

模型将被放置在应用程序的 app 目录中。默认情况下,模型类是空的。我们不必显式地告诉 Eloquent 模型它对应哪个表,因为它会假设数据库表是模型名称的复数形式。因此,在这种情况下,Task 模型被假定为与 tasks 数据库表对应。

让我们为此模型添加一些内容。首先,我们将声明模型上的 name 属性应该是“可批量赋值的”:

php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    /**
     * 可批量赋值的属性。
     *
     * @var array
     */
    protected $fillable = ['name'];
}

我们将在为应用程序添加路由时了解更多关于如何使用 Eloquent 模型的信息。当然,随时查阅 完整的 Eloquent 文档 以获取更多信息。

Eloquent 关系

现在我们的模型已经定义,我们需要将它们链接起来。例如,我们的 User 可以有多个 Task 实例,而 Task 被分配给一个 User。定义关系将允许我们流畅地遍历我们的关系,如下所示:

php
$user = App\User::find(1);

foreach ($user->tasks as $task) {
	echo $task->name;
}

tasks 关系

首先,让我们在我们的 User 模型上定义 tasks 关系。Eloquent 关系被定义为模型上的方法。Eloquent 支持几种不同类型的关系,因此请务必查阅 完整的 Eloquent 文档 以获取更多信息。在这种情况下,我们将在 User 模型上定义一个调用 Eloquent 提供的 hasMany 方法的 tasks 函数:

php
<?php

namespace App;

// 命名空间导入...

class User extends Model implements AuthenticatableContract,
                                    AuthorizableContract,
                                    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword;

    // 其他 Eloquent 属性...

    /**
     * 获取用户的所有任务。
     */
    public function tasks()
    {
        return $this->hasMany(Task::class);
    }
}

user 关系

接下来,让我们在 Task 模型上定义 user 关系。同样,我们将关系定义为模型上的方法。在这种情况下,我们将使用 Eloquent 提供的 belongsTo 方法来定义关系:

php
<?php

namespace App;

use App\User;
use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    /**
     * 可批量赋值的属性。
     *
     * @var array
     */
    protected $fillable = ['name'];

    /**
     * 获取拥有任务的用户。
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

太好了!现在我们的关系已经定义,我们可以开始构建我们的控制器了!

路由

在我们任务列表应用程序的 基础版本 中,我们在 routes.php 文件中使用闭包定义了所有逻辑。对于此应用程序的大部分内容,我们将使用 控制器 来组织我们的路由。控制器将允许我们将 HTTP 请求处理逻辑分散到多个文件中,以便更好地组织。

显示视图

我们将有一个使用闭包的单一路由:我们的 / 路由,它将只是应用程序访客的登录页面。因此,让我们填写我们的 / 路由。从此路由,我们希望渲染一个包含“欢迎”页面的 HTML 模板:

在 Laravel 中,所有 HTML 模板都存储在 resources/views 目录中,我们可以使用 view 助手从我们的路由返回这些模板之一:

php
Route::get('/', function () {
	return view('welcome');
});

当然,我们需要实际定义此视图。我们稍后会这样做!

认证

请记住,我们还需要让用户创建账户并登录到我们的应用程序。通常,在 Web 应用程序中构建整个认证层可能是一项繁琐的任务。然而,由于这是一个常见的需求,Laravel 试图使这个过程变得完全轻松。

首先,请注意您的 Laravel 应用程序中已经包含了一个 app/Http/Controllers/Auth/AuthController。此控制器使用一个特殊的 AuthenticatesAndRegistersUsers trait,其中包含创建和认证用户所需的所有逻辑。

认证路由

那么,我们还需要做什么?好吧,我们仍然需要创建注册和登录模板,并定义指向认证控制器的路由。首先,让我们将我们需要的路由添加到我们的 app/Http/routes.php 文件中:

php
// 认证路由...
Route::get('auth/login', 'Auth\AuthController@getLogin');
Route::post('auth/login', 'Auth\AuthController@postLogin');
Route::get('auth/logout', 'Auth\AuthController@getLogout');

// 注册路由...
Route::get('auth/register', 'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');

认证视图

认证要求我们在 resources/views/auth 目录中创建 login.blade.phpregister.blade.php。当然,这些视图的设计和样式并不重要;但是,它们至少应该包含一些基本字段。

register.blade.php 文件应包含一个表单,其中包括 nameemailpasswordpassword_confirmation 字段,并向 /auth/register 路由发出 POST 请求。

login.blade.php 文件应包含一个表单,其中包括 emailpassword 字段,并向 /auth/login 发出 POST 请求。

NOTE

如果您想查看这些视图的完整示例,请记住,整个应用程序的源代码可在 GitHub 上获取。

任务控制器

既然我们知道我们需要检索和存储任务,让我们使用 Artisan CLI 创建一个 TaskController,它将把新控制器放在 app/Http/Controllers 目录中:

php
php artisan make:controller TaskController --plain

现在控制器已经生成,让我们继续在我们的 app/Http/routes.php 文件中为控制器指向一些路由:

php
Route::get('/tasks', 'TaskController@index');
Route::post('/task', 'TaskController@store');
Route::delete('/task/{task}', 'TaskController@destroy');

认证所有任务路由

对于此应用程序,我们希望所有任务路由都需要经过认证的用户。换句话说,用户必须“登录”到应用程序才能创建任务。因此,我们需要将对任务路由的访问限制为仅经过认证的用户。Laravel 使用 中间件 使这变得轻而易举。

要为控制器上的所有操作要求经过认证的用户,我们可以从控制器的构造函数中添加对 middleware 方法的调用。所有可用的路由中间件都在 app/Http/Kernel.php 文件中定义。在这种情况下,我们希望将 auth 中间件分配给控制器上的所有操作:

php
<?php

namespace App\Http\Controllers;

use App\Http\Requests;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class TaskController extends Controller
{
    /**
     * 创建一个新的控制器实例。
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }
}

构建布局和视图

此应用程序只有一个视图,其中包含一个用于添加新任务的表单以及所有当前任务的列表。为了帮助您可视化视图,以下是应用基本 Bootstrap CSS 样式的完成应用程序的截图:

应用程序图像

定义布局

几乎所有的 Web 应用程序在页面之间共享相同的布局。例如,此应用程序有一个顶部导航栏,通常会出现在每个页面上(如果我们有多个页面)。Laravel 使得使用 Blade 布局 在每个页面之间共享这些常见功能变得容易。

正如我们之前讨论的,所有 Laravel 视图都存储在 resources/views 中。因此,让我们在 resources/views/layouts/app.blade.php 中定义一个新的布局视图。.blade.php 扩展名指示框架使用 Blade 模板引擎 来渲染视图。当然,您可以在 Laravel 中使用纯 PHP 模板。然而,Blade 提供了便捷的快捷方式来编写更清晰、简洁的模板。

我们的 app.blade.php 视图应如下所示:

php
// resources/views/layouts/app.blade.php

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Laravel Quickstart - Intermediate</title>

		<!-- CSS JavaScript -->
	</head>

	<body>
		<div class="container">
			<nav class="navbar navbar-default">
				<!-- 导航栏内容 -->
			</nav>
		</div>

		@yield('content')
	</body>
</html>

请注意布局中的 @yield('content') 部分。这是一个特殊的 Blade 指令,指定所有扩展布局的子页面可以在其中注入自己的内容。接下来,让我们定义将使用此布局并提供其主要内容的子视图。

定义子视图

太好了,我们的应用程序布局已经完成。接下来,我们需要定义一个视图,其中包含一个用于创建新任务的表单以及一个列出所有现有任务的表格。让我们在 resources/views/tasks/index.blade.php 中定义此视图,它将对应于我们的 TaskController 中的 index 方法。

我们将跳过一些 Bootstrap CSS 样板代码,只关注重要的部分。请记住,您可以在 GitHub 上下载此应用程序的完整源代码:

php
// resources/views/tasks/index.blade.php

@extends('layouts.app')

@section('content')

    <!-- Bootstrap 样板... -->

	<div class="panel-body">
        <!-- 显示验证错误 -->
		@include('common.errors')

		<!-- 新任务表单 -->
		<form action="/task" method="POST" class="form-horizontal">
			{{ csrf_field() }}

            <!-- 任务名称 -->
			<div class="form-group">
				<label for="task-name" class="col-sm-3 control-label">任务</label>

				<div class="col-sm-6">
					<input type="text" name="name" id="task-name" class="form-control">
				</div>
			</div>

            <!-- 添加任务按钮 -->
			<div class="form-group">
				<div class="col-sm-offset-3 col-sm-6">
					<button type="submit" class="btn btn-default">
						<i class="fa fa-plus"></i> 添加任务
					</button>
				</div>
			</div>
		</form>
	</div>

	<!-- TODO: 当前任务 -->
@endsection

一些解释说明

在继续之前,让我们稍微谈谈这个模板。首先,@extends 指令通知 Blade 我们正在使用在 resources/views/layouts/app.blade.php 中定义的布局。所有在 @section('content')@endsection 之间的内容将被注入到 app.blade.php 布局中的 @yield('content') 指令的位置。

现在我们已经为我们的应用程序定义了一个基本布局和视图。让我们继续从我们的 TaskControllerindex 方法返回此视图:

php
/**
 * 显示用户的所有任务列表。
 *
 * @param  Request  $request
 * @return Response
 */
public function index(Request $request)
{
	return view('tasks.index');
}

接下来,我们准备向我们的 POST /task 路由的控制器方法添加代码,以处理传入的表单输入并向数据库添加新任务。

NOTE

@include('common.errors') 指令将加载位于 resources/views/common/errors.blade.php 的模板。我们尚未定义此模板,但我们很快就会这样做!

添加任务

验证

现在我们在视图中有一个表单,我们需要向我们的 TaskController@store 方法添加代码,以验证传入的表单输入并创建一个新任务。首先,让我们验证输入。

对于此表单,我们将使 name 字段为必填项,并声明它必须包含少于 255 个字符。如果验证失败,我们希望将用户重定向回 /tasks URL,并将旧输入和错误闪存到 session 中:

php
/**
 * 创建一个新任务。
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
	$this->validate($request, [
		'name' => 'required|max:255',
	]);

	// 创建任务...
}

如果您跟随 基础快速入门,您会注意到此验证代码看起来有些不同!由于我们在控制器中,我们可以利用包含在 Laravel 基础控制器中的 ValidatesRequests trait 的便利。此 trait 暴露了一个简单的 validate 方法,该方法接受请求和一组验证规则。

我们甚至不必手动确定验证是否失败或进行手动重定向。如果给定规则的验证失败,用户将自动被重定向回他们来的地方,并且错误将自动闪存到会话中。不错!

$errors 变量

请记住,我们在视图中使用了 @include('common.errors') 指令来呈现表单的验证错误。common.errors 将允许我们在所有页面上以相同的格式轻松显示验证错误。现在让我们定义此视图的内容:

php
// resources/views/common/errors.blade.php

@if (count($errors) > 0)
    <!-- 表单错误列表 -->
    <div class="alert alert-danger">
        <strong>哎呀!出了点问题!</strong>

        <br><br>

        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

NOTE

$errors 变量在 每个 Laravel 视图中都可用。如果没有验证错误,它将只是一个空的 ViewErrorBag 实例。

创建任务

现在输入验证已经处理完毕,让我们通过继续填写我们的路由来实际创建一个新任务。一旦新任务创建完成,我们将用户重定向回 /tasks URL。为了创建任务,我们将利用 Eloquent 关系的强大功能。

大多数 Laravel 的关系都暴露了一个 create 方法,该方法接受一组属性,并将在将其存储到数据库之前自动设置相关模型上的外键值。在这种情况下,create 方法将自动将给定任务的 user_id 属性设置为当前经过认证的用户的 ID,我们通过 $request->user() 访问:

php
/**
 * 创建一个新任务。
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
    $this->validate($request, [
        'name' => 'required|max:255',
    ]);

    $request->user()->tasks()->create([
        'name' => $request->name,
    ]);

    return redirect('/tasks');
}

太好了!我们现在可以成功创建任务。接下来,让我们继续通过构建所有现有任务的列表来添加到我们的视图中。

显示现有任务

首先,我们需要编辑我们的 TaskController@index 方法,以将所有现有任务传递给视图。view 函数接受第二个参数,该参数是一个数据数组,将在视图中可用,其中数组中的每个键将在视图中成为一个变量。例如,我们可以这样做:

php
/**
 * 显示用户的所有任务列表。
 *
 * @param  Request  $request
 * @return Response
 */
public function index(Request $request)
{
	$tasks = Task::where('user_id', $request->user()->id)->get();

    return view('tasks.index', [
        'tasks' => $tasks,
    ]);
}

然而,让我们探索一些 Laravel 的依赖注入功能,以将 TaskRepository 注入到我们的 TaskController 中,我们将使用它进行所有数据访问。

依赖注入

Laravel 的 服务容器 是整个框架中最强大的功能之一。在阅读本快速入门后,请务必阅读容器的所有文档。

创建存储库

正如我们之前提到的,我们希望定义一个 TaskRepository,其中包含 Task 模型的所有数据访问逻辑。如果应用程序增长并且您需要在应用程序中共享一些 Eloquent 查询,这将特别有用。

因此,让我们创建一个 app/Repositories 目录并添加一个 TaskRepository 类。请记住,所有 Laravel app 文件夹都使用 PSR-4 自动加载标准自动加载,因此您可以根据需要创建任意数量的额外目录:

php
<?php

namespace App\Repositories;

use App\User;
use App\Task;

class TaskRepository
{
    /**
     * 获取给定用户的所有任务。
     *
     * @param  User  $user
     * @return Collection
     */
    public function forUser(User $user)
    {
        return Task::where('user_id', $user->id)
                    ->orderBy('created_at', 'asc')
                    ->get();
    }
}

注入存储库

一旦我们的存储库定义好,我们可以简单地在我们的 TaskController 的构造函数中“类型提示”它,并在我们的 index 路由中使用它。由于 Laravel 使用容器解析所有控制器,我们的依赖项将自动注入到控制器实例中:

php
<?php

namespace App\Http\Controllers;

use App\Task;
use App\Http\Requests;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Repositories\TaskRepository;

class TaskController extends Controller
{
    /**
     * 任务存储库实例。
     *
     * @var TaskRepository
     */
    protected $tasks;

    /**
     * 创建一个新的控制器实例。
     *
     * @param  TaskRepository  $tasks
     * @return void
     */
    public function __construct(TaskRepository $tasks)
    {
        $this->middleware('auth');

        $this->tasks = $tasks;
    }

    /**
     * 显示用户的所有任务列表。
     *
     * @param  Request  $request
     * @return Response
     */
    public function index(Request $request)
    {
        return view('tasks.index', [
            'tasks' => $this->tasks->forUser($request->user()),
        ]);
    }
}

显示任务

一旦数据传递,我们可以在我们的 tasks/index.blade.php 视图中遍历任务并在表格中显示它们。@foreach Blade 构造允许我们编写简洁的循环,这些循环编译为快速的纯 PHP 代码:

php
@extends('layouts.app')

@section('content')
    <!-- 创建任务表单... -->

    <!-- 当前任务 -->
    @if (count($tasks) > 0)
        <div class="panel panel-default">
            <div class="panel-heading">
                当前任务
            </div>

            <div class="panel-body">
                <table class="table table-striped task-table">

                    <!-- 表头 -->
                    <thead>
                        <th>任务</th>
                        <th>&nbsp;</th>
                    </thead>

                    <!-- 表体 -->
                    <tbody>
                        @foreach ($tasks as $task)
                            <tr>
                                <!-- 任务名称 -->
                                <td class="table-text">
                                    <div>{{ $task->name }}</div>
                                </td>

                                <td>
                                    <!-- TODO: 删除按钮 -->
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
        </div>
    @endif
@endsection

我们的任务应用程序几乎完成了。但是,当任务完成时,我们没有办法删除现有任务。接下来让我们添加这个功能!

删除任务

添加删除按钮

我们在代码中留下了一个“TODO”注释,指出删除按钮应该在哪里。因此,让我们在 tasks/index.blade.php 视图中的任务列表的每一行中添加一个删除按钮。我们将为列表中的每个任务创建一个小的单按钮表单。当按钮被点击时,将向应用程序发送一个 DELETE /task 请求,这将触发我们的 TaskController@destroy 方法:

php
<tr>
    <!-- 任务名称 -->
    <td class="table-text">
        <div>{{ $task->name }}</div>
    </td>

    <!-- 删除按钮 -->
    <td>
        <form action="/task/{{ $task->id }}" method="POST">
            {{ csrf_field() }}
            {{ method_field('DELETE') }}

            <button>删除任务</button>
        </form>
    </td>
</tr>

关于方法欺骗的说明

请注意,删除按钮的表单 method 列为 POST,即使我们使用 Route::delete 路由响应请求。HTML 表单仅允许 GETPOST HTTP 动词,因此我们需要一种方法来从表单中欺骗 DELETE 请求。

我们可以通过在表单中输出 method_field('DELETE') 函数的结果来欺骗 DELETE 请求。此函数生成一个隐藏的表单输入,Laravel 识别并将用于覆盖实际的 HTTP 请求方法。生成的字段将如下所示:

php
<input type="hidden" name="_method" value="DELETE">

路由模型绑定

现在,我们几乎准备好在我们的 TaskController 上定义 destroy 方法了。但是,首先,让我们重新审视此路由的路由声明:

php
Route::delete('/task/{task}', 'TaskController@destroy');

无需添加任何额外的代码,Laravel 将把给定的任务 ID 注入到 TaskController@destroy 方法中,如下所示:

php
/**
 * 销毁给定的任务。
 *
 * @param  Request  $request
 * @param  string  $taskId
 * @return Response
 */
public function destroy(Request $request, $taskId)
{
	//
}

然而,我们在此方法中需要做的第一件事是使用给定的 ID 从数据库中检索 Task 实例。因此,如果 Laravel 可以直接注入与 ID 匹配的 Task 实例,那不是很好吗?让我们实现它!

在您的 app/Providers/RouteServiceProvider.php 文件的 boot 方法中,让我们添加以下代码行:

php
$router->model('task', 'App\Task');

这行小代码将指示 Laravel 在看到路由声明中的 {task} 时检索与给定 ID 对应的 Task 模型。现在我们可以这样定义我们的 destroy 方法:

php
/**
 * 销毁给定的任务。
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    //
}

授权

现在,我们已经将 Task 实例注入到我们的 destroy 方法中;然而,我们没有保证经过认证的用户实际上“拥有”给定的任务。例如,可能会有一个恶意请求试图通过向 /tasks/{task} URL 传递一个随机任务 ID 来删除其他用户的任务。因此,我们需要使用 Laravel 的授权功能来确保经过认证的用户实际上拥有注入到路由中的 Task 实例。

创建策略

Laravel 使用“策略”将授权逻辑组织到简单的小类中。通常,每个策略对应一个模型。因此,让我们使用 Artisan CLI 创建一个 TaskPolicy,它将生成的文件放在 app/Policies/TaskPolicy.php 中:

php
php artisan make:policy TaskPolicy

接下来,让我们向策略添加一个 destroy 方法。此方法将接收一个 User 实例和一个 Task 实例。该方法应简单地检查用户的 ID 是否与任务上的 user_id 匹配。实际上,所有策略方法都应返回 truefalse

php
<?php

namespace App\Policies;

use App\User;
use App\Task;
use Illuminate\Auth\Access\HandlesAuthorization;

class TaskPolicy
{
    use HandlesAuthorization;

    /**
     * 确定给定用户是否可以删除给定任务。
     *
     * @param  User  $user
     * @param  Task  $task
     * @return bool
     */
    public function destroy(User $user, Task $task)
    {
        return $user->id === $task->user_id;
    }
}

最后,我们需要将我们的 Task 模型与我们的 TaskPolicy 关联。我们可以通过在 app/Providers/AuthServiceProvider.php 文件的 $policies 属性中添加一行来实现。这将通知 Laravel 每当我们尝试对 Task 实例授权操作时应使用哪个策略:

php
/**
 * 应用程序的策略映射。
 *
 * @var array
 */
protected $policies = [
    Task::class => TaskPolicy::class,
];

授权操作

现在我们的策略已经编写好,让我们在我们的 destroy 方法中使用它。所有 Laravel 控制器都可以调用一个 authorize 方法,该方法由 AuthorizesRequest trait 暴露:

php
/**
 * 销毁给定的任务。
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    $this->authorize('destroy', $task);

    // 删除任务...
}

让我们稍微检查一下这个方法调用。传递给 authorize 方法的第一个参数是我们希望调用的策略方法的名称。第二个参数是我们当前关注的模型实例。请记住,我们最近告诉 Laravel 我们的 Task 模型对应于我们的 TaskPolicy,因此框架知道在哪个策略上触发 destroy 方法。当前用户将自动发送到策略方法,因此我们不需要在此手动传递它。

如果操作被授权,我们的代码将继续正常执行。然而,如果操作未被授权(意味着策略的 destroy 方法返回 false),将抛出 403 异常,并向用户显示错误页面。

NOTE

还有几种其他方法可以与 Laravel 提供的授权服务进行交互。请务必浏览完整的 授权文档

删除任务

最后,让我们完成向我们的 destroy 方法添加逻辑以实际删除给定任务。我们可以使用 Eloquent 的 delete 方法删除数据库中的给定模型实例。一旦记录被删除,我们将用户重定向回 /tasks URL:

php
/**
 * 销毁给定的任务。
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    $this->authorize('destroy', $task);

    $task->delete();

    return redirect('/tasks');
}