iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 24
0
tags: 2019鐵人賽 Laravel Controller PHP Magic Methods

前言

Laravel 是一個非常遵守 MVC 設計模式的開發框架,所以為了符合這種設計模式,我們應該使用一個 Controller 的 class 來掌控網路請求的行為,而不是在將所有網路請求的處理邏輯放在 route 檔案中的閉包處理。
我們利用 $ php artisan make:controller YourControllerName 的 Controllers 都存放在 app/Http/Controllers 的資料夾中

開始使用 Controller

  1. 我們可以利用 $ php artisan make:controller TestController 創建一個乾淨的控制器。

  2. 接著我們在這個控制器裡面新增一個方法,名為:greet

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TestController extends Controller
{
    //
    public function greet()
    {
        echo 'Hello world';
    }
}
  1. 定義 route 執行內容到指定的 controller action
Route::get('greet', 'TestController@greet');

控制器與命名空間(Controllers & Namespaces)

我們定義控制器路由時,不需要指定完整的控制器命名空間。這是因為 RouteServiceProvider 載入你的路由檔案內的路由組裡已經包含了命名空間,所以我們只要指定 App\Http\Controllers 之後的類別名稱即可。

For Example:
假設你有一個 controller class 在 App\Http\Controllers\Photos\AdminController,那路由器到控制器的設定如下

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

單一操作的控制器 - 魔術方法 __invoke()

  1. 如果你的控制器只需要處理一個動作,那麼你可以直接用 php artisan make:controller --invokeable

  2. 創建出來的控制器會包含一個 __invoke 的方法

<?php

namespace App\Http\Controllers;

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

class ShowProfile extends Controller
{
    public function __invoke($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}
  1. route 設定也不用指定方法
Route::get('user/{id}', 'ShowProfile');

其實 __invoke() 是屬於 PHP 5.3.0 以上版本的語法,當你用類似呼叫函式的方法調用一個 class,__invoke() 這個方法會自動被調用。

如果對於 __invoke 運作模式還有很多疑問的話,請參考 PHP 魔術方法,這一切都是 magic !

控制器的中介層 - 魔術方法 __construct()

我在Day 22 - Laravel Middleware 篇有介紹到,如果你想在控制器的路由中加入中介層,設定方法如下

Route::middleware('auth')->get('profile', 'UserController@show');
//或者是 Route::get('profile', 'UserController@show')->middleware('auth');

但是 Laravel 提供了另外一種更方便的呼叫方法!

1. 新創建的控制器必須繼承最基層的 Controller class

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class middleController extends Controller // <<< 必須繼承自 Controller
{
	public function show()
	{
		echo 'show';
	}
}

2. 利用魔術方法 __construct(),將中介層加入控制器

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class middleController extends Controller // <<< 必須繼承自 Controller
{
	public function __construct()
	{
		// 我這裡延用前天文章中創建的兩個中介層
		$this->middleware('before');  
		$this->middleware('after');
	}
	public function show()
	{
		echo 'show';
	}
}

__construct():當物件生成的同時,強制先去實作 __construct() 內的功能。

關於 __construct() 使用方法有疑惑的建議參考

3. 利用閉包加入中介層

Laravel 提供了一個更便利的方式來為控制器定義中介層,而不用再定義完整的中介層

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class middleController extends Controller // <<< 必須繼承自 Controller
{
	public function __construct()
	{
            $this->middleware(function ($request, $next)         
            {
                // do something...
                return $next($request);
            });
	}
    
	public function show()
	{
		echo 'show';
	}
}

這個獨立的中介層就只適用這個控制器,如果有其他控制器想使用,就還是乖乖建立一個完整的中介層吧!

4. 對中介層作出限制

在控制器中設定中介層時,可以再加入 only('function name') 或者 except('function name') 來指定或排除需要經過中介層的方法。

For Example:
只有 route 指向 sayHiToAdmin 時才需要經過 IsAdmin 的中介層。

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class middleController extends Controller
{
    //

    public function __construct()
    {
        $this->middleware('before');
        $this->middleware('after');
        $this->middleware('Is_Admin')->only('sayHiToAdmin');
    }

    public function show()
    {
        echo 'show';
    }

    public function sayHiToAdmin(Request $request)
    {
        echo 'Hi, '.$request->name;
    }
}

資源控制器

如果要設定一個「CRUD」路由,光是資料庫存取就至少要設定四個路由。Laravel 可以只透過一行程式碼就將典型的「CRUD」路由指定給控制器。

這裡官方文件寫的還滿清楚的,我就不再舉例說明。

Dependency Injection & Controllers

Dependency Injection 是一種降低程式高耦合的設計模式,這個概念有點抽象,我不多做說明,我直接解釋他的用法。

有興趣的可以參考

1. 我們在 class 外,透過 use 將相依物件引入。

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;  // <<< 這裡就是所謂的「相依物件」(Dependency)

class UserController extends Controller
{
    //...略
}

2. 透過 __construct() 把相依物件注入 class

被宣告的相依物件將會自動解析與注入到控制器實例中

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * 使用者 repository 實例
     */
    protected $users;

    /**
     * 建立新控制器實例。
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
}

Method Injection

由於 Laravel 4 只有 constructor injection,所以只要 class 要實現依賴注入,唯一的管道就是 constructor injection,若有些相依物件只有單一 method 使用一次,也必須使用 constructor injection,這將導致最後 constructor 的參數爆炸而難以維護。

對於一些只有單一 method 使用的相依物件,若能只在 method 的參數加上 type-hint,就可自動依賴注入,而不需要動用 constructor,那就太好了,這就是 method injection。

1. 使用方式:

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request; // <<< 這裡就是所謂的「相依物件」(Dependency)
class UserController extends Controller
{
    public function store(Request $request)
    // 不用再透過 '__construct()' 就只要參數前面加入 type-hint 就可以直接將相依物件注入
    {
        $name = $request->name;
        //
    }
}

2. 在方法注入後,額外的輸入參數

如果預期從 route 會提供一個參數,那麼只要在你其它的依賴之後列出 route 參數即可。

假設我們的 route 設定如下

Route::put('user/{id}', 'UserController@update');

那我們的控制器內的方法設定如下

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
    public function update(Request $request, $id)
    {
        //
    }
}

上一篇
Day 23 - Laravel CSRF 篇
下一篇
Day 25 - Laravel Validation 篇
系列文
新手後端工程師的學習歷程30

尚未有邦友留言

立即登入留言