Prettus Repository (儲存庫模式)
Prettus\Repository
介紹
Prettus\Repository 是一個在 Laravel 中實現 Repository
Pattern(儲存庫模式)的套件。它的目的是解耦應用程式的商業邏輯與資料存取層,提升代碼的可維護性與可測試性,同時簡化資料查詢、篩選、排序等操作。
使用目的
- 資料存取抽象化:將資料存取邏輯與業務邏輯分離,方便維護與測試。
- 簡化資料查詢:提供簡潔 API 實現 CRUD 操作。
- 支援資料轉換:與
Transformable 和
TransformableTrait 結合使用,可統一輸出格式。
- 可擴展性高:支援複雜查詢與關聯操作,並可擴展 repository 層。
優點
- 清晰結構:控制器聚焦業務邏輯,資料存取交由 Repository 管理。
- 提升測試性:易於替換資料來源並模擬資料存取,利於單元測試。
- 支援多種資料源:例如資料庫、API、外部服務等。
- 簡化操作:內建常見的查詢功能如分頁、排序、篩選等。
- 集中邏輯:統一資料操作邏輯位置,有利於維護與擴展。
缺點
- 學習曲線偏高:對初學者來說結構較為複雜。
- 效能損耗:增加額外抽象層,可能帶來輕微效能開銷。
- 過度設計風險:在小型專案中可能顯得冗餘。
設計方向
- 使用 Criteria 解耦跨模型的共用查詢條件(如
status = active、tenant_id 等)。
- 使用 Presenter 將資料轉換為前端格式,避免 controller 手動轉換資料。
- Repository 聚焦單一模型的資料存取與邏輯封裝。
使用情境
- 中大型應用:資料層結構複雜,需集中管理存取邏輯。
- 多資料來源:如多資料庫或 API 整合情境。
- 需大量單元測試:抽象化資料層有助於測試。
電子商務範例
定義資料模型
定義 Repository Interface 與實作
綁定至服務容器
控制器中使用 Repository
資料轉換(可選)
查詢功能與其他技巧
Scopes(模型範圍查詢)
Criteria(封裝查詢條件)
分頁
排序
指定欄位查詢
限制返回筆數
條件查詢
關聯查詢(Eager Loading)
原始查詢
資料轉換與映射
自定義查詢方法
Scope 與 Criteria 比較
| 特性 |
Scope |
Criteria |
| 使用範圍 |
模型內,適用簡單查詢 |
repository 層,適用複雜查詢 |
| 封裝方式 |
寫在 Eloquent 模型方法中 |
封裝成獨立類別,可重複使用 |
| 用途 |
簡單條件(如 active) |
複雜查詢邏輯,跨模型共用 |
| 可重用性 |
侷限於單一模型 |
可跨 repository 與模型重用 |
| 查詢方式 |
使用 where、scope... 方法組合查詢 |
使用 pushCriteria 將條件加入查詢中 |
| 適用場景 |
單純欄位查詢 |
多欄位條件或複合邏輯查詢 |
Prettus Repository (儲存庫模式)
Prettus\Repository
介紹
Prettus\Repository是一個在 Laravel 中實現 Repository Pattern(儲存庫模式)的套件。它的目的是解耦應用程式的商業邏輯與資料存取層,提升代碼的可維護性與可測試性,同時簡化資料查詢、篩選、排序等操作。使用目的
Transformable和TransformableTrait結合使用,可統一輸出格式。優點
缺點
設計方向
status = active、tenant_id等)。使用情境
電子商務範例
定義資料模型
// Product.php class Product extends Model { protected $fillable = ['name', 'price', 'stock']; public function orders() { return $this->belongsToMany(Order::class); } }定義 Repository Interface 與實作
// ProductRepositoryInterface.php namespace App\Repositories; use Prettus\Repository\Contracts\RepositoryInterface; interface ProductRepositoryInterface extends RepositoryInterface { public function getProductsByCategory($categoryId); } // ProductRepository.php namespace App\Repositories; use Prettus\Repository\Eloquent\BaseRepository; use App\Models\Product; class ProductRepository extends BaseRepository implements ProductRepositoryInterface { public function model() { return Product::class; } public function getProductsByCategory($categoryId) { return $this->model->where('category_id', $categoryId)->get(); } }綁定至服務容器
// AppServiceProvider.php public function register() { $this->app->bind(ProductRepositoryInterface::class, ProductRepository::class); }控制器中使用 Repository
// ProductController.php namespace App\Http\Controllers; use App\Repositories\ProductRepositoryInterface; class ProductController extends Controller { protected $productRepository; public function __construct(ProductRepositoryInterface $productRepository) { $this->productRepository = $productRepository; } public function show($categoryId) { $products = $this->productRepository->getProductsByCategory($categoryId); return response()->json($products); } }資料轉換(可選)
// Product.php use Prettus\Repository\Contracts\Transformable; use Prettus\Repository\Traits\TransformableTrait; class Product extends Model implements Transformable { use TransformableTrait; } // ProductTransformer.php namespace App\Transformers; use League\Fractal\TransformerAbstract; use App\Models\Product; class ProductTransformer extends TransformerAbstract { public function transform(Product $product) { return [ 'id' => $product->id, 'name' => $product->name, 'price' => $product->price, 'stock' => $product->stock, ]; } } // ProductRepository 中使用 use App\Transformers\ProductTransformer; public function getProductsByCategory($categoryId) { $products = $this->model->where('category_id', $categoryId)->get(); return fractal($products, new ProductTransformer())->toArray(); }查詢功能與其他技巧
Scopes(模型範圍查詢)
// 模型中定義 public function scopeActive($query) { return $query->where('status', 'active'); } public function scopeInCategory($query, $categoryId) { return $query->where('category_id', $categoryId); } // 使用方式 $products = $this->model->active()->inCategory($categoryId)->get();Criteria(封裝查詢條件)
use Prettus\Repository\Contracts\CriteriaInterface; use Prettus\Repository\Contracts\RepositoryInterface; class ActiveCriteria implements CriteriaInterface { public function apply($model, RepositoryInterface $repository) { return $model->where('status', 'active'); } } $this->repository->pushCriteria(new ActiveCriteria()); $products = $this->repository->all();分頁
public function getPagedProducts($categoryId, $perPage = 15) { return $this->model->where('category_id', $categoryId)->paginate($perPage); }排序
$products = $this->model->orderBy('price', 'asc')->get(); $products = $this->model->orderBy('created_at', 'desc')->get(); $products = $this->model->orderBy($request->sortBy, $request->order)->get();指定欄位查詢
$products = $this->model->select('id', 'name', 'price')->get();限制返回筆數
$products = $this->model->limit(10)->get();條件查詢
$products = $this->model->where('category_id', $categoryId) ->where('price', '>', 100) ->get(); $products = $this->model->where([ ['status', '=', 'active'], ['stock', '>', 0] ])->get();關聯查詢(Eager Loading)
$products = $this->model->with('category')->get();原始查詢
$products = DB::select('SELECT * FROM products WHERE price > ?', [100]);資料轉換與映射
use App\Transformers\ProductTransformer; use Prettus\Repository\Facades\Fractal; $products = $this->model->all(); $transformedProducts = Fractal::collection($products, new ProductTransformer())->toArray();自定義查詢方法
public function getDiscountedProducts() { return $this->model->where('discount', '>', 0)->get(); }Scope 與 Criteria 比較
active)where、scope...方法組合查詢pushCriteria將條件加入查詢中