Laravel 文件閱讀:控制器

zhangbao發表於2017-09-18

翻譯、衍生自:https://learnku.com/docs/laravel/5.5/controllers

我們的業務程式碼不能都寫在路由檔案(比如:routes/web.php)裡。路由的形式有兩種:基於閉包和基於控制器的。我們最好把一些相關的路由請求放在一個控制器內管理,對應的,業務程式碼也轉移到了控制器裡。Larave 的控制器位於 app/Http/Controllers 目錄下。

基礎控制器

定義控制器

下面,就是一個基礎控制器。這個控制器繼承了 Laravel 提供的基類控制器。在基類控制器中提供了幾個便捷的方法可以使用,比如 middleware 這個方法,就是用來給 控制器 action 附加中介軟體的。

<?php

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class UsersController extends Controller
{
    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        return view('users.profile', ['user' => User::findOrFail($id)]);
    }
}

指向這個控制器 action 的路由是這樣定義的:

Route::get('users/{id}', 'UserController@show');

當瀏覽器發起 users/xxx 路徑請求的時候,UsersControllershow 方法就會被呼叫,路由引數也會傳遞給這個方法。

需要注意的是, 我們的控制器不是非要繼承基類控制器的,只不過,如果不繼承基類控制器的話,一些非常好用的方法,比如 middlewarevalidatedispatch 就無法使用了。

控制器 & 名稱空間

細心觀察的話會發現,我們在定義控制器路由的時候,並沒有使用控制器的完整名稱空間。這是因為在 RouteServiceProvider 中,已經為 routes/web.phproutes/api.php 檔案中的所有路由預設了 App\Http\Controllers 的名稱空間字首,所以我們在定義控制器路由時,只需指定 App\Http\Controllers 後面那部分的內容即可。

如果 App\Http\Controllers 目錄中的控制器有更深的巢狀層級,比如,我們有一個控制器的完整路徑是 App\Http\Controllers\Photos\AdminController,那麼在註冊控制器路由的時候,就要像這樣:

Route::get('foo', 'Photos\AdminController@method');

單 Action 控制器

有時,一個控制器只有唯一的一個方法,那麼這時只要在控制器中定義一個 __invoke 方法就行:

<?php

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class ShowProfile extends Controller
{
    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function __invoke($id)
    {
        return view('users.profile', ['user' => User::findOrFail($id)]);
    }
}

指向這個單 Action 控制器的路由,定義起來就變成下面這樣了:

Route::get('users/{id}', 'ShowProfile');

控制器中介軟體

在我們的路由檔案裡,可以為控制器路由指定中介軟體:

Route::get('profile', 'UsersController@show')->middleware('auth');

但是,在控制器構造方法裡指定中介軟體更加方便,直接使用 middleware 方法,就可以輕鬆地為我們的控制器 action 分配中介軟體了:

class UsersController extends Controller
{
    /**
     * Instantiate a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');

        $this->middleware('log')->only('index');

        $this->middleware('subscribed')->except('store');
    }
}

在控制器中也可以使用閉包來註冊一箇中介軟體。這為只在一個控制器中使用的中介軟體帶來便利,無需定義一箇中介軟體類了:

$this->middleware(function ($request, $next) {
    // ...

    return $next($request);
});

我們有時會對控制器的一個 action 子集做中介軟體處理。但這可能說明你的控制器是臃腫的,嘗試去把這個控制器拆分成多個、職能更加單一的控制器吧。

資源控制器

資源控制器就是指包含 indexcreatestoreshoweditupdatedestroy 這 7 個方法的控制器。之所以稱為「資源控制器」,是因為這 7 個方法包括了操作某個資源所需的全部基本操作,也就是 CRUD 操作。

在 Laravel 中,在使用 Artisan 命令 make:controller 時,通過傳遞一個額外選項 --resource 就可以快速建立這樣一個資源控制器,我們以操作圖片資源的 PhotosController 為例:

php artisan make:controller PhotosController --resource

這樣就在 app/Http/Controllers/PhotosController.php 的地方建立了一個控制器,接下來在為資源控制器註冊資源路由。

Route::resource('photos', 'PhotosController');

通過這一句程式碼,我們就定義了 7 個指向到 PhotosController 中相應方法的所有路由。我們來看一下:

對應到資源控制器相應方法的 7 個資源路由。

請求型別 URI 控制器 Action 路由別名
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

指定資源模型

在生成資源控制器的時候,還可以通過指定 -model 選項,為控制器中的方法指定模型型別。

php artisan make:controller --resource --model=Photo

表單方法偽造

由於 HTML 表單不能發出 PUTPATCHDELETE 型別的請求,所以想要使用這些請求型別的話,就必須得偽造。Laravel 中使用隱藏表單域 _method 來告訴應用程式我們的請求型別。我們可以通過 method_field 輔助函式來生成這個表單域:

{{ method_field('PUT') }}

或者手動新增:

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

都是一樣的。

部分資源路由

我們在使用 Route::resource 定義資源路由的時候,並不想生成全部 7 個路由,比如只需要定義其中的 2 個或者 4 個路由,Route::resource 同樣可以滿足這樣的需求,這時需要為它指定第三個引數:

Route::resource('photos', 'PhotosController', ['only' => [
    'index', 'show'
]]);

Route::resource('photos', 'PhotosController', ['except' => [
    'create', 'store', 'update', 'destroy'
]]);

命名資源路由

預設,所有的資源路由都有一個名字,當然你也可以按情況覆蓋掉相應方法。這是需要用到 names 這個陣列選項:

Route::resource('photos', 'PhotosController', ['names' => [
    'create' => 'photos.build'
]]);

命名資源路由引數

預設,所有的資源路由的路由引數是使用資源名的「單數」形式(如果資源名本身是單數形式,那麼路由引數直接使用單數形式)。

Route::resource('users', 'AdminUserController');

// 生成的 `show` 路由
/users/{user}

Route::resource('user', 'AdminUserController');
// 生成的 `show` 路由
/user/{user}

當然,你也可以覆蓋這個約定。這時要用到 parameters 這個陣列選項:

Route::resource('users', 'AdminUserController', ['parameters' => [
    'users' => 'admin_user'
]]);

// 生成的 `show` 路由
/users/{admin_user}

本地化資源 URIs

預設,Route::resource 生成的路由地址中包含 createedit 這兩個英語動詞。如果需要本地化這兩個動詞,需要使用 Route::resourceVerbs 方法,這個方法可以在 AppServiceProviderboot 方法裡設定:

use Illuminate\Support\Facades\Route;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Route::resourceVerbs([
        'create' => 'crear',
        'edit' => 'editar',
    ]);
}

定義好後,使用 Route::resource('fotos', 'PhotoController') 生成的資源路由會產生下面兩個的 URIs:

/fotos/crear

/fotos/{foto}/editar

fotos、crear 和 editar 是西班牙語裡的照片、建立和編輯。

補充資源控制器

如果你還要定義除了預設 7 個資源路由之外的其他路由,那麼你應該在呼叫 Route::resource 之前定義這些路由,否則通過 resource 方法定義的路由可能會意外覆蓋你額外新增的路由:

Route::get('photos/popular', 'PhotoController@method');

Route::resource('photos', 'PhotoController');

記住,要保證你的控制器職能單一,如果發現除了一些在資源控制器中普遍存在的方法之外,還需要定義一些其他的方法,那麼請考慮將控制器拆分成兩個或者多個更小的控制器。

依賴注入 & 控制器

通過構造方法注入

Laravel 中的控制器都是使用服務容器解析的。所以,你可以在控制器的構造方法裡使用依賴注入。宣告的依賴會自動注入到控制器例項中:

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UsersController extends Controller
{
    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
}

Laravel 契約類也支援依賴注入。 有時,注入這些依賴可能會讓應用程式有更好可測試性。

方法注入

除了在構造方法裡注入,還可以在控制器方法裡注入。一個常見的使用方式是將 Illuminate\Http\Request 例項注入到控制器方法裡:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UsersController extends Controller
{
    /**
     * Store a new user.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        $name = $request->name;

        //
    }
}

如果路由包含引數,那麼這些引數要列在這些依賴項之後。例如,如果路由是這樣的:

Route::put('users/{id}', 'UsersController@update');

在控制器方法裡,就要在 Illuminate\Http\Request 依賴宣告之後,獲取我們們的使用者 ID:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UsersController extends Controller
{
    /**
     * Update the given user.
     *
     * @param  Request  $request
     * @param  string  $id
     * @return Response
     */
    public function update(Request $request, $id)
    {
        //
    }
}

路由快取

先宣告!使用閉包形式定義的路由時無法快取的。要想用快取,必須將閉包路由轉換為控制器路由。

如果應用程式中,全部使用的是基於控制器方法的路由,那麼這時候就可以充分利用 Laravel 路由快取的功能了!使用路由快取可以大大地減少應用程式中註冊所有路由花費的時間。有些情況,註冊路由的過程可能會提升 100 倍!生成路由快取,執行 Artisan 命令 route:cache 即可:

php artisan route:cache

執行這條命令後,路由快取檔案會在每次請求時被載入。需要注意的是,應用程式中每當新增了一個新的路由,都需要重新執行 route:cache 命令方可生效。因此,你應該只在專案部署時才執行 route:cache 命令

清除路由快取使用 route:clear 就 OK 了!

php artisan route:clear

相關文章