中級任務清單

簡介

這個快速入門導引為 Laravel 框架提供了進階的介紹,內容概括了資料庫遷移、Eloquent ORM、路由、認證、授權、依賴注入、驗證、視圖,及 Blade 樣板。如果你已經熟悉 Laravel 框架的基礎或 PHP 框架,那麼這會是個很好的起始點。

要在 Laravel 功能中為樣本做基本的選擇,我們會建構一個簡單的任務清單,可以使用它追蹤所有想完成的任務(典型的「代辦事項清單」範例)。與「基本」快速入門相比,此教學會允許使用者在應用程式建立帳號並認證。此專案完整並完成的原始碼在 GitHub 上

安裝

當然,首先你需要一個全新安裝的 Laravel 框架。你可以選擇使用 Homestead 虛擬機器或是其他本機 PHP 環境來執行框架。只要你的本機環境準備好,就可以使用 Composer 安裝 Laravel 框架:

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

你可以自由的只閱讀快速入門的剩餘部分;不過,如果你想下載這個快速入門的原始碼並在你的本機機器執行,那麼你需要克隆它的 Git 資源庫並安裝依賴:

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 指令列介面可以被用於產生各種類別,為你建構 Laravel 專案時節省大量的手動輸入。在此例中,讓我們使用 make:migration 指令為 tasks 資料表產生新的資料庫遷移:

php artisan make:migration create_tasks_table --create=tasks

此遷移會被放置於你專案的 database/migrations 目錄中。你可能已經注意到,make:migration 指令已經增加了自動遞增的 ID 及時間戳記至遷移檔。讓我們編輯這個檔案並為任務的名稱增加額外的 string 欄位,也增加連結 tasksusers 資料表的 user_id 欄位:

<?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 artisan migrate

這個指令會建立我們所有的資料表。如果你使用你選擇的資料庫客戶端檢查資料表,你應該看到新的 tasksusers 資料表,並包含了我們遷移中所定義的欄位。接著,我們已經準備好定義我們的 Eloquent ORM 模型!

Eloquent 模型

Eloquent 是 Laravel 預設的 ORM(物件關聯對映)。Eloqunet 透過明確的定義「模型」,讓你無痛的在資料庫取得及儲存資料。一般情況下,每個 Eloqunet 模型會直接對應於一張資料表。

User 模型

首先,我們需要對應至 users 資料表的模型。不過,如果你看過你專案的 app 目錄,你會發現 Laravel 已經附帶了一個 User 模型,所以我們不必手動產生。

Task 模型

所以,讓我們定義一個對應至 tasks 資料表的 Task 模型。同樣的,我們可以使用 Artisan 指令來產生此模型。在此例中,我們會使用 make:model 指令:

php artisan make:model Task

這個模型會放置在你應用程式的 app 目錄中。預設中,此模型類別會是空的。我們不必明確告知 Eloquent 模型要對應哪張資料表,因為它會假設資料表是模型名稱的複數型態。所以,在此例中,Task 模型會假設對應至 tasks 資料表。

讓我們增加一些東西至模型。首先,我們需要宣告模型的 name 屬性應該能被「批量賦值」:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    /**
     * 這些屬性能被批量賦值。
     *
     * @var array
     */
    protected $fillable = ['name'];
}

在為我們的應用程式增加路由時,我們會學習更多關於如何使用 Eloquent 模型。當然,你可以很自由的參考完整的 Eloquent 文件取得更多資訊。

Eloquent 關聯

現在我們的模型已經定義好,我們需要將他們連結在一起。例如,我們的 User 可以擁有多筆 Task 實例,而一筆 Task 則被賦予給一名 User。定義關聯可以讓我們流暢的走過關聯,像這樣:

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

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

tasks 關聯

首先,讓我們在 User 模型定義 tasks 的關聯。Eloquent 關聯被定義為模型中的方法。Eloquent 支援多種不同類型的關聯,所以請務必查閱完整的 Eloquent 文件取得更多資訊。在本例中,我們會在 User 模型中定義一個 tasks 函式,並呼叫 Eloquent 提供的 hasMany 方法:

<?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

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 輔助方法來回傳這些樣板的其中一個:

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

當然,我們必須確實的定義這些視圖。我們將在稍後完成!

認證

還記得我們需要讓使用者建立帳號並登入至我們的應用程式。一般來說,為網頁應用程式建構完整的認證是相當乏味的工作。不過,因為它是一個通用的需求,所以 Laravel 試著讓這個過程變得無痛。

首先,你會注意到在應用程式中已經包含一個 app/Http/Controllers/Auth/AuthController。這個控制器使用了特別的 AuthenticatesAndRegistersUsers trait,它包含了所有建立及認證使用者必要的邏輯。

認證路由

所以,還有哪些部分是留著讓我們做的?好的,我們依然需要建立註冊及登入模板,與定義指向認證控制器的路由。首先,讓我們在我們的 app/Http/routes.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 欄位,並製造一個 POST 請求至 /auth/register 路由。

login.blade.php 檔案必需包含一個表單,包含 emailpassword 欄位,並製造一個 POST 請求至 /auth/login

注意:如果你想查看這些視圖的完整範例,請記得應用程式的完整原始碼可以在 GitHub 上取得

任務控制器

因為我們已經知道我們需要取得及儲存任務,所以讓我們使用 Artisan 指令列介面建立一個 TaskController,這個新的控制器會放置在 app/Http/Controllers 目錄中:

php artisan make:controller TaskController --plain

現在這個控制器已經被產生,讓我們繼續在 app/Http/routes.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

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 樣式:

應用程式圖片

定義佈局

幾乎所有的網頁應用程式都會在不同頁面共用相同的佈局。舉個例子,應用程式通常在每個頁面(如果我們有一個以上)的頂部都擁有導航欄。Laravel 使用了 Blade 佈局讓不同頁面共用這些相同的功能。

如同我們前面討論,Laravel 所有的視圖都被儲存在 resources/views。所以,讓我們定義一個新的佈局視圖至 resources/views/layouts/app.blade.php.blade.php 副檔名會告知框架使用 Blade 模板引擎渲染此視圖。當然,你可以在 Laravel 使用純 PHP 的樣板。不過,Blade 提供了方便的簡寫來撰寫乾淨、簡潔的模板。

我們的 app.blade.php 視圖看起來應該如下:

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

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Laravel 快速入門 - 進階</title>

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

    <body>
        <div class="container">
            <nav class="navbar navbar-default">
                <!-- Navbar 內容 -->
            </nav>
        </div>

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

注意佈局中的 @yield('content') 部分。這是特別的 Blade 指令,讓子頁面可以在此處注入自己的內容以延伸佈局。接著,讓我們定義將會使用此佈局並提供主要內容的子視圖。

定義子視圖

很好,我們的應用程式佈局已經完成。接下來,我們需要定義包含建立任務的表單及列出已有任務表格的視圖。讓我們將此視圖定義在 resources/views/tasks/index.blade.php,它會對應至我們 TaskControllerindex 方法。

我們會跳過一些 Bootstrap CSS 樣板,只專注在重要的事物上。切記,你可以在 GitHub 下載應用程式的完整原始碼:

// 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>

    <!-- 代辦:目前任務 -->
@endsection

一些注意事項的說明

在繼續之前,讓我們談談有關模板的一些事項。首先 @extends 指令會告知 Blade,我們使用定義於 resources/views/layouts/app.blade.php 的佈局。所有在 @section('content')@endsection 之間的內容會被注入到 app.blade.php 佈局中的 @yield('content') 位置裡。

現在我們已經為我們的應用程式定義了基本的佈局及視圖。接著讓我們在 TaskControllerindex 方法回傳此視圖:

/**
 * 顯示使用者所有任務的清單。
 *
 * @param  Request  $request
 * @return Response
 */
public function index(Request $request)
{
    return view('tasks.index');
}

接著,我們已經準備好增加程式碼至我們的 POST /task 路由的控制器方法,以處理接收到的表單輸入並增加新的任務至資料庫中。

注意:@include('common.errors') 指令會載入位於 resources/views/common/errors.blade.php 的模板。我們尚未定義此模板,但是我們將會在稍後定義它!

增加任務

驗證

現在我們視圖中已經有一個表單,我們需要增加程式碼至我們的 TaskController@store 方法來驗證接收到的表單輸入並建立新的任務。首先,讓我們驗證輸入。

對此表單來說,我們要讓 name 欄位為必填,且它必須少於 255 字元。如果驗證失敗,我們會將使用者重導回 / URL,並將舊的輸入及錯誤訊息快閃至 session

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

    // Create The Task...
}

如果你跟隨過基本快速入門,你會注意到驗證的程式碼相比起來有些不同!因為我們在控制器內,所以我們可以利用 Laravel 基底控制器所包含方便的 ValidatesRequests trait。這個 trait 提供了一個簡單的 validate 方法,它接收一個請求與包含驗證規則的陣列。

我們不必再手動判斷當驗證失敗時需手動重導。如果給定的規則驗證失敗,使用者會自動被重導回原本的位置,並自動將錯誤訊息快閃至 session 中。很好!

$errors 變數

記得我們在視圖中使用了 @include('common.errors') 指令來渲染表單的驗證錯誤訊息。common.errors 讓我們可以簡單的在我們所有的頁面顯示相同格式的驗證錯誤訊息。現在讓我們定義此視圖的內容:

// 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

注意:errors 變數可用於每個 Laravel 的視圖中。如果沒有驗證錯誤訊息存在,那麼它就會是一個空的 ViewErrorBag 實例。

建立任務

現在輸入已經被驗證處理完畢。讓我們繼續填寫我們的路由來實際的建立一筆新的任務。一旦新的任務被建立後,我們會將使用者重導回 /tasks URL。要建立該任務,我們會充分的利用 Eloquent 的關聯功能。

Laravel 大部分的關聯提供了一個 create 方法,它接收一個包含屬性的陣列,並會在儲存至資料庫前自動設置關聯模型的外鍵值。在此例中,create 方法會自動將給定任務的 user_id 屬性設置為目前已驗證使用者的 ID,因為我們透過 $request->user() 存取。

/**
 * 建立新的任務。
 *
 * @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 函式接收一個能在視圖中取用之資料的陣列作為第二個參數,陣列中的每個鍵都會在視圖中作為變數。For example, we could do this:

/**
 * Display a list of all of the user's task.
 *
 * @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

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

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 指令結構讓我們可以撰寫簡潔的迴圈,並編譯成快速的純 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>Task</th>
                        <th>&nbsp;</th>
                    </thead>

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

                                <td>
                                   <!-- 代辦:刪除按鈕 -->
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
        </div>
    @endif
@endsection

我們任務應用程式大部分都完成了。但是,當我們完成已有的任務後,還沒有任何方式可以刪除它們。接著讓我們增加此功能!

刪除任務

增加刪除按鈕

我們在我們的程式碼中應該放刪除按鈕的地方留下了「待辦」的事項。所以,讓我們在 tasks/index.blade.php 視圖中列出任務的每一行增加一個刪除按鈕。我們會為列表中的每個任務建立一個只有單個按鈕的小表單。當按鈕被按下時,一個 DELETE /task 的請求將會被發送到應用程式,它會觸發我們的 TaskController@destroy 方法:

<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 請求方法。產生的欄位看起來如下:

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

路由模型綁定

現在,我們大致上已經準備好定義我們 TaskControllerdestroy 方法。但是首先,讓我們重新審視我們為它宣告的路由:

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

不增加任何額外的程式碼,Laravel 會注入給定的任務 ID 至 TaskController@destroy 方法中,如下:

/**
 * Destroy the given task.
 *
 * @param  Request  $request
 * @param  string  $taskId
 * @return Response
 */
public function destroy(Request $request, $taskId)
{
    //
}

但是,我們要在這個方法中做的第一件事,就是透過給定的 ID 從資料庫中取得 Task 實例。所以,如果 Laravel 可以先注入與 ID 符合的 Task 實例,那豈不是很棒?讓我們做到這一點!

在你的 app/Providers/RouteServiceProvider.php 檔案的 boot 方法中,讓我們增加下方這行程式碼:

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

這一小行的程式碼會告知 Laravel,若在路由宣告中看見 {task},就會取得與給定 ID 對應的 Task 模型。現在我們可以定義我們的 destroy 方法,如下:

/**
 * 卸除給定的任務。
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    //
}

認證

現在,我們有一個注入至 destroy 方法的 Task 實例;然而,我們不能保證通過認證的使用者實際上「擁有」給定的任務。舉個例子,一個惡意的請求可能透過傳遞一個隨機任務 ID 至 /tasks/{task} URL,企圖嘗試刪除其他使用者的任務。所以,我們需要使用 Laravel 的授權功能,以確保已認證的使用者實際上擁有注入至路由的 Task 實例。

建立一個原則

Laravel 使用了「原則」將授權邏輯組織至簡單,小型的類別。一般來說,每個原則會對應至一個模型。所以,讓我們使用 Artisan 指令列介面建立一個 TaskPolicy,產生的檔案會被放置於 app/Policies/TaskPolicy.php

php artisan make:policy TaskPolicy

接著,讓我們增加一個 destroy 方法治原則中。此方法會取得一個 User 實例及一個 Task 實例。此方法會簡單的檢查當使用者的 ID 符合任務的 user_id。實際上,所有的授權方法必須回傳 truefalse

<?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 實例的行為時該用哪個原則:

/**
 * 應用程式的原則對應。
 *
 * @var array
 */
protected $policies = [
    Task::class => TaskPolicy::class,
];

授權行為

現在我們的原則已經撰寫完,讓我們在我們的 destroy 方法中使用它。Laravel 所有的控制器可以呼叫一個 authorize 方法,它由 AuthorizesRequest trait 所提供:

/**
 * 卸除給定的任務。
 *
 * @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 例外並顯示錯誤頁面給使用者。

注意:Laravel 提供的授權服務還有多種方式可供互動。請務必瀏覽完整的授權文件

刪除該任務

最後,讓我們完成增加邏輯至我們的 destroy 方法來實際刪除給定的任務。我們可以使用 Eloquent 的 delete 方法從資料庫中刪除給定的模型實例。一旦記錄被刪除,我們會將使用者重導回 tasks URL:

/**
 * 卸除給定的任務。
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    $this->authorize('destroy', $task);

    $task->delete();

    return redirect('/tasks');
}