如何使用 PhpStorm 將 new 重構成依賴注入?
為了可測試性與低耦合,我們會使用依賴注入
取代 new
建立物件,對於 legacy code,當然可以使用手動的方式重構,透過 PhpStorm,我們可以使用更簡單的方式將 new
重構成 依賴注入
。
Motivation
若使用 TDD 方式開發,為了隔離測試,一定會使用依賴注入建立物件,在深入探討依賴注入一文中曾以可測試性角度探討之,不過在實務上,一直沒有以工具的角度探討如何實踐,本文將使用 PhpStorm,實際將 legacy code 的 new
重構成 依賴注入
。
Version
PHP 7.0.0
Laravel 5.2.39
PhpStorm 2016.1.2
實際案例
PostService.php 1 1GitHub Commit : 建立 PostService()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16namespace App\Services;
use App\Repositories\PostRepository;
use Illuminate\Database\Eloquent\Collection;
class PostService
{
/**
* @return Collection
*/
public function showLatest3Posts()
{
$postRepository = new PostRepository();
return $postRepository->getLatest3Posts();
}
}
在 legacy code,我們常會發現 class 直接在 method 內被 new
,以執行結果角度而言是對的,但有以下兩個問題 :
- 由於
PostRepository
在showLatest3Posts()
內直接被new
,因此無法對PostRepository
做隔離,導致無法對PostService
做單元測試。 - 由於
PostRepository
在showLatest3Posts()
內直接被new
,導致PostService
直接相依於PostRepository
,耦合性太高,無法被抽換,違反 SOLID 原則的開放封閉原則
與依賴反轉原則
。
要解決這兩個問題,又不影響執行結果,最簡單的方式就是將 new
重構
成依賴注入
。
Extract Field
將來要將 $postRepository
重構成 field,使用 constructor injection 方式。
將滑鼠游標放在 $postRepository
變數上,按熱鍵 ⌃ + T,出現 Refactor This
選單,選擇 Extract Field
。
可以重構成兩種方式,因為將使用 constructor injection 來建立物件,不需要 new
,因此選擇 $postRepository
。
PhpStorm 會自動使用原來變數名稱為 field 名稱。
- Initialize in : 選
Current method
。 - Visibility : 選
private
。
注意 Initialize in
並不是選擇 Class constructor
,因為我們要用的是 constructor injection,而不是在 constructor 去 new
一個物件給 field。
PhpStorm 幫我們加上了 field,這是我們預期的。
但 PhpStorm 一樣是在 method 去 new
產生物件,只是改成 field,這不是我們想要的。
將 method 內有 new
的一行手動刪除。
在 field 加上 PHPDoc 描述型別。
在 PhpStorm 要替 field 加上 PHPDoc 很簡單,只要在 field 上面輸入 /**
,再按 ␣ 空白鍵,就會出現 /** @var */
。
在 @var
之後輸入型別,輸入 Po
之後就會出現語法提示讓你用挑的。
在 field 加上型別描述,為 constructor injection 的 type hint
Generate Constructor
按熱鍵 ⌘ + N,出現 Generate
選單,選擇 Constructor
。
選擇你要使用依賴注入的 field。
PhpStorm 自動幫我們在 constructor 產生依賴注入。
PostService.php 2 2GitHub Commit : 將 PostService() 重構成依賴注入1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27namespace App\Services;
use App\Repositories\PostRepository;
use Illuminate\Database\Eloquent\Collection;
class PostService
{
/** @var PostRepository */
private $postRepository;
/**
* PostService constructor.
* @param PostRepository $postRepository
*/
public function __construct(PostRepository $postRepository)
{
$this->postRepository = $postRepository;
}
/**
* @return Collection
*/
public function showLatest3Posts()
{
return $this->postRepository->getLatest3Posts();
}
}
單元測試
馬上跑單元測試,綠燈 打完收工。
PostServiceTest.php 3 3GitHub Commit : 單元測試 : 建立 PostServiceTest1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25use App\Post;
use App\Services\PostService;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class PostServiceTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function 顯示最新3筆文章()
{
/** arrange */
factory(Post::class, 100)->create();
/** act */
$actual = App::make(PostService::class)
->showLatest3Posts()
->pluck('id')
->all();
/** assert */
$expected = [100, 99, 98];
$this->assertEquals($expected, $actual);
}
}
Conclusion
- Legacy code 蠻免會使用
new
去建立物件,為了降低耦合度與增加可測試性,勢必改用依賴注入,透過 PhpStorm 的重構,可以快速地將new
重構成依賴注入,非常方便。
Sample Code
完整的範例可以在我的 GitHub 上找到。