# laravel Repository (資源庫)
https://dotblogs.com.tw/regionbbs/2017/07/18/simple_talk_about_repository_pattern
https://iter01.com/561104.html
https://nicole929chan.wordpress.com/2018/11/20/%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F-repository-pattern/
https://www.kancloud.cn/curder/laravel/408484
Laravel 的 Service Container 提供了 DI(Dependency injection)功能,方便我們在各個元件之間自動注入相關的 Class。
所以建議用這種
Repository Pattern
## 介紹
若将数据库逻辑都写在 Model 里,会造成 model 代码的臃肿难以维护,基于 **SOLID** 原则,我们应该使用 Repository 模式辅助 Model,将相关的数据库逻辑封装在不同的 Repository,方便后期项目的维护。
**Notice: Model 原則上只留 relation,盡量不要有取資料的邏輯。**
`Post::whereIn('category_id',[1,2])->where('is_draft',0)->orderBy('created_at', 'desc')->take(5)->get();`
其實它由3部分組成.
第一是Post資料模型;
第二個是`whereIn('category_id',[1,2])->where('is_draft',0)->orderBy('created_at', 'desc')->take(5)`,資料操作條件;
第三個是get()資料獲取的方法;
我們知道,**Eloquent**裡有個**Query Scope**,可以用來把第二部分,也就是查詢條件精簡。所以,在使用了**Query Scope**後,我們可以把精簡成:
`Post::ofCategory([1,2])->isDraft()->orderBy('created_at', 'desc')->take(5)->get();`
咋一看上去,好像也沒怎麼精簡啊,但實際上你已經實現程式碼解耦和複用了,比如說**isDraft()**, 這個程式碼可以到處用,而不用擔心耦合問題。
精簡程度和你的邏輯抽象程度有關,比如說你完全可以寫成:
`Post::findPosts([1,2],0,'desc',5)->get();`
在輕型專案中,強烈推薦使用Query Scope,這是一種良好的程式設計習慣。
在更復雜的專案中,**Query Scope**就不夠用了,因為它和資料模型還是一種強耦合,**Repository Pattern**就是要把第一,第二,第三部分全部解耦;
**倉庫指的就是一個倉庫介面的實現;這裡定義你的業務邏輯**
### 詳細解答
很多應用程式都有資料存取的需求,而且有絕大多數使用的都是關聯式資料庫 (RDBMS),長久以來,也有絕大多數的開發人員使用傳統的資料庫存取介面連結並使用資料庫,舉凡 ODBC, DB-Library, ADO, ADO.NET, OLE DB 等不同的介面,再搭配 SQL 指令 (不管是 PL/SQL, Transact-SQL, ANSI SQL 或是 Jet SQL) 來操作資料,大多數的開發人員也很習慣這一點,事實上,小型的應用程式通常只有一種資料來源:資料庫,鮮少會有一個應用程式有多種資料儲存的介面,但是現在隨著應用程式的雲端化、分散化與多樣性,應用程式的資料來源再也不只一種,例如使用 NoSQL、經由訊息介面 (Messaging) 作通訊、接取資訊進行處理等工作,使得應用程式要面對的資料類型愈來愈多,若是再繼續使用傳統的作法 (將應用程式邏輯與資料存取介面緊密結合),將會使得應用程式在面對資料時受限,影響應用程式的發展性。
為了要整合這麼多種資料儲存類型,最好的方法就是將要儲存的物件抽取出來變成一個物件實體,這個基本上就是三隻豬設計法 (Domain Driven Design, DDD) 裡面所稱的 Entity (當然 Value Object 也可以存在資料儲存體),你也可以稱它為領域物件 (Domain Object),有了領域物件後,就可以試著將對資料儲存體互動的介面抽象化,並傳遞領域物件到這個介面,以達到儲存或擷取領域物件的能力,這個與資料儲存體互動的抽象化介面,就是大家經常看到的 Repository。
也就是說,Repository 其實是存取資料的抽象化 API,只要是向資料儲存體放資料或要資料的功能,都會經由它來處理,有了抽象化的 API 後,上層的服務 (Service) 只要關心 Repository 的抽象化 API,至於它的實作? 只要由真正負責執行資料存取的程式碼實作,並搭配 Dependency Injection (DI) 技術於適當的時候注入到服務內即可,如果資料儲存體換了,只要抽換掉 Repository API 的實作即可,上層的服務完全不需要異動。而這也是 Repository 最終的目的。
實作 Repository 最快的方法,目前公認是 ORM (Object Relational Mapping) 技術,而且現在已經有很多實作品,例如 Entity Framework、Hibernate 等實作,可有效率的將物件與資料庫的存取連動操作,再加上大多數的資料操作都是以 CRUD 居多,造就了 Generic Repository 的實作
Repository (或是其他的 Design Pattern) 的存在,都是為了要解決一個特定場域的問題,或許資料儲存體沒有經常抽換的可能,但是 Repository 和 Factory, Strategy 這些 Pattern 一樣,幾乎是隨手就能實作的 Pattern,而且只要應用得當,能讓應用程式獲取更寬廣的空間,何樂而不為呢?
## 使用Repository 不用 ORM原因
就我的觀點而言,如果是一個不需要抽換資料儲存體的應用程式,確實可以不實作 Repository API,連 Generic Repository 都可以省了,直接操作 ORM 或是資料儲存 API 就能做完工作,但是萬一有一天,老闆要你把 SQL Server 換成 MongoDB (採用 eventually consistent 以加速處理),或是把檔案換成資料庫,恭喜,那些資料存取的程式就準備重寫吧。
## ServiceProvider中繫結介面
ServiceProvider是Laravel IOC容器實現動態換介面實現的地方,所以我們在這裡繫結一下,這樣我們在使用的時候,不直接使用介面實現,而是用ioc容器解析介面,它會幫你自動找到對應好的實現。這就意味著,以後需要更換實現,可以在這裡更換
## 使用倉庫
```
use App\Repositories\Interfaces\PostInterface;
public function __construct(PostInterface $post){ $this->postRepo = $post; }
```
## 完整演變
使用laravel在操作資料庫,常常用eloquent的方式,很習慣直接在Controller下語法
操作單一資料庫還可以,若要操作多個資料庫,或是使用不同的方式,例如PDO,就不太適合直接在Controller操作資料,因為資料的操作跟Controller關係太過緊密,要修code全得在Controller作修正。
換成Repository的方式,把「存取資料」從Controller中抽出來,放在其他地方實作,而Controller只須DI Repository就行,隨時替換其他的Repository也沒問題。觀念這一篇概念寫的超清楚。
以下步驟「將資料存取從Controller中替換成Repository方式」,並且「透過抽象化讓所有Repository共享通用性函數」。
範例以Eloquent方式做資料存取,並且從Interface開始(這是OOP的精神 XD)。
**步驟1: interface TopicRepository 定義被具象類別實作的存取資料方法。**
**步驟2: EloquentTopicRepository 實作 TopicRepository 的方法。**
```
// app/Repositories/Contracts/TopicRepository.php
namespace App\Repositories\Contracts;
interface TopicRepository
{
public function all();
}
// app/Repositories/Eloquent/EloquentTopicRepository.php
namespace App\Repositories\Eloquent;
use App\Topic;
class EloquentTopicRepository implements TopRepository
{
public function all()
{
return Topic::all();
}
}
```
**步驟3: TopicController 相依注入 TopicRepository。**
這裡要注意,相依注入的類別不是**EloquentTopicRepository(具象類別)**,如果是注入具象類別就無法把具象類別與Controller解耦合,而是注入**TopicRepository(Interface)**,才能夠隨時抽換Repository
```
// app/Http/Controllers/TopicController.php
use App\Repositories\Contracts\TopicRepository;
class TopicController extends Controller
{
protected $topics;
public function __construct(TopicRepository $topics)
{
$this->topics = $topics;
}
}
```
**以上無法直接執行,因為TopicRepository是介面(interface),無法被實例化,所以必須綁定它與實作的具象類別(EloquentTopicRepository)。**
**步驟4: RepositoryServiceProvider 設定兩者的綁定。**
// app/Providers/RepositoryServiceProvider.php
use App\Repositories\Contracts\TopicRepository;
use App\Repositories\Eloquent\EloquentTopicRepository;
```
class RepositoryServiceProvider extends ServiceProvider
{
public function boot()
{
$this->app->bind(TopicRePository::class, EloquentTopicRepository::class);
}
}
// config/app.php
// 註冊 RepositoryServiceProvide
```
透過在RepositoryServiceProvider做的綁定,當TopicController相依注入時自然會實例化EloquentTopicRepository;而所謂的repository替換,是指在RepositoryServiceProvider進行,把boot()的code改掉就能替換,不需要修改Controller的code。
## Repository Pattern
https://blog.johnsonlu.org/repository-pattern-and-service-layer/
Repository Pattern 做法很簡單,概念上就是將 Model 取資料的邏輯拆到 Repository 中,讓 Model 變成真正的 Pure-Model,而實作上搭配 DI(Dependency injection)就輕鬆許多。
**Model 範例:app/Models/Album.php**
```
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Album extends Model
{
protected $primaryKey = 'album_id';
protected $table = 'album';
public function songs()
{
return $this->hasMany('App\Models\Song', 'album_id');
}
}
```
**Repository 範例:app/Repositories/AlbumRepository.php**
```
namespace App\Repositories;
use App\Models\Album;
class AlbumRepository
{
protected $album;
// 透過 DI 注入 Model
public function __construct(Album $album)
{
$this->album = $album;
}
// 取資料邏輯:取得公司為 Sony 的專輯
public function getSonyAlbum($limit = 1)
{
return $this->album
->where('album_company', 'Sony')
->limit($limit)
->get();
}
}
```
這樣一來,Controller 就不會直接跟 Model 依賴了。
**Controller 範例**
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Repositories\AlbumRepository;
class TestController extends Controller
{
protected $albumRepo;
透過 DI 注入 Repository
public function __construct(AlbumRepository $albumRepo)
{
$this->albumRepo = $albumRepo;
}
public function test(Request $request)
{
// 直接呼叫 Repository 包裝好的 method
return $this->albumRepo->getSonyAlbum(10);
}
}
###### tags: `Laravel`