利用 Service Container 從 Route 將參數傳給 Controller

實務上我們可能會遇到資料庫邏輯與商業邏輯完全相同,只有顯示邏輯不同,也就是 controller、service、repository 完全相同,只有 route 與 view 不相同,我們該如何在使用同一個 controller 的前提下,實現此需求呢?

Version


Laravel 5.2.29

測試案例


我們以 TDD 方式來完成此需求。

  1. URI 為 /welcome 時,使用 welcome.blade.php
  2. URI 為 /helloworld 時,使用 helloworld.blade.php

第一個測試


HomeControllerTest.php1 1GitHub Commit : 新增HomeControllerTest的第一個測試

tests/Unit/HomeControllerTest.php
1
2
3
4
5
6
7
8
9
class HomeControllerTest extends TestCase
{

/** @test */
public function welcomeURI()
{

$this->visit('/welcome')
->see('Laravel 5');
}
}

我們希望當URI為 /welcome 時,期望能在 view 看到 Laravel 5 字串。

馬上跑測試,我們得到第1個 紅燈

錯誤訊息為 A request [http://localhost/welcome] failed

因為我們還沒有建立 route。

routes.php2 2GitHub Commit : 修改routes.php,增加/welcome

app/routes.php
1
2
3
4
5
6
7
8
Route::get('/', function () {
return view('welcome');
});

Route::get('/welcome', [
'as' => 'Welcome',
'uses' => 'HomeController@index'
]);

補上 route 後,繼續跑測試。

我們得到第2個 紅燈

錯誤訊息為 Class App\Http\Controllers\HomeController does not exist

因為我們還沒建立 HomeController

HomeController.php3 3GitHub Commit : 新增HomeController.php

app/Http/Controllers/HomeController.php
1
2
3
4
5
6
7
8
9
10
11
namespace App\Http\Controllers;

use App\Http\Requests;

class HomeController extends Controller
{

public function index()
{

return view('Welcome');
}
}

新增 HomeController 後,繼續跑測試。

我們得到第1個 綠燈

第二個測試


HomeControllerTest.php4 4GitHub Commit : 新增HomeControllerTest的第二個測試

tests/Unit/HomeControllerTest.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class HomeControllerTest extends TestCase
{

/** @test */
public function welcomeURI()
{

$this->visit('/welcome')
->see('Laravel 5');
}

/** @test */
public function helloWorld()
{

$this->visit('/helloworld')
->see('Hello World');
}
}

馬上跑測試,我們得到第1個 紅燈

錯誤訊息為 A request [http://localhost/helloworld] failed

因為我們還沒有建立 route。

routes.php5 5GitHub Commit : 修改routes.php,增加/helloworld

app/routes.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use App\Http\Controllers\HomeController;

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

Route::get('/welcome', [
'as' => 'Welcome', function () {
return App::make(HomeController::class)->index('welcome');
}
]);

Route::get('/helloworld', [
'as' => 'HelloWorld', function () {
return App::make(HomeController::class)->index('helloworld');
}
]);

修改 routes 時,我們面臨了一個挑戰,需求為 route 與 view 不同,但 controller 相同,因此勢必使用同一個 HomeController@index

也就是若我們可以將 view 當成參數,從 route 傳給 HomeController@index,就能達成我們的需求。

我們將陣列的 key 由 uses 改成 closure ,利用App::make()自己建立 HomeController物件,並將 view 為 index() 的參數傳入。

HomeController.php6 6GitHub Commit : 修改HomeController.php,新增$viewName參數

app/Http/Controllers/HomeController.php
1
2
3
4
5
6
7
8
9
10
11
namespace App\Http\Controllers;

use App\Http\Requests;

class HomeController extends Controller
{

public function index($viewName)
{

return view($viewName);
}
}

由於 routes.php 將 view的名稱傳入 HomeController@index,所以必須重構符合 routes.php的要求。

重構 HomeController.php後,繼續跑測試。

我們得到第2個 紅燈

錯誤訊息為 View [HelloWorld] not found

因為我們還沒建立 helloworld.blade.php

新增helloworld.blade.php後,繼續跑測試。

我們得到2個 綠燈,兩個測試都通過了。

Conclusion


  • 我們也可以自行由 routes.php 去建立 controller 物件,只是因為 Laravel 內部大量使用依賴注入,所以你無法自行使用 new 去建立,但透過 App::make() 與 service container,我們就可以再次掌握 controller 物件,因此可以透過 route 對 controller 傳參數。

Sample Code


完整的範例可以在我的GitHub上找到。

2016-04-12