肥 Controller 瘦不下來?Laravel 後台架構終極對決:Repository vs. Action 模式,資深工程師帶你選對屠龍刀!
嗨,我是浪花科技的 Eric。身為一個整天跟程式碼打交道的工程師,我看過太多令人頭皮發麻的 Laravel Controller。一個 `store` 方法洋洋灑灑寫了兩百行,裡面混雜著資料驗證、檔案上傳、商業邏輯、資料庫操作,甚至還順便發了幾封 Email 通知。每次要修改一小塊功能,都得像拆炸彈一樣小心翼翼,深怕動到不該動的地方,然後整個系統就跟你鬧脾氣,直接掛掉。
這就是所謂的「肥控制器 (Fat Controller)」,是專案走向維護地獄的特快車。今天,我不是要來抱怨的(好吧,可能有一點),而是要帶大家深入探討兩種在 Laravel 社群中廣受討論,用來幫 Controller 瘦身的強大設計模式:Repository Pattern 和 Action Pattern。這不是一場誰好誰壞的零和遊戲,而是一場關於「在對的場景,選擇對的工具」的深度思辨。準備好了嗎?讓我們一起來為你的 Laravel Admin 後台架構做個徹底的健康檢查!
一切的萬惡之源:肥控制器 (The Fat Controller) 的詛咒
在我們開始尋找解藥之前,得先搞清楚病灶在哪。什麼是肥控制器?簡單來說,就是把所有不該它管的事情,全都塞進一個 Controller 方法裡。讓我們看一個血淋淋的例子,假設我們正在建立一個商品管理的後台:
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Product;
use Illuminate\Support\Facades\Storage;
use App\Notifications\ProductCreatedNotification;
class ProductController extends Controller
{
public function store(Request $request)
{
// 1. 資料驗證
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'price' => 'required|numeric|min:0',
'image' => 'required|image|mimes:jpeg,png,jpg|max:2048',
]);
// 2. 檔案上傳
$path = $request->file('image')->store('products', 'public');
// 3. 建立資料庫紀錄
$product = new Product();
$product->name = $validatedData['name'];
$product->price = $validatedData['price'];
$product->image_path = $path;
$product->save();
// 4. 觸發商業邏輯 (例如:通知管理員)
$admin = User::find(1);
$admin->notify(new ProductCreatedNotification($product));
// 5. 其他邏輯... (更新庫存、寫入 Log...)
// ...
return redirect()->route('admin.products.index')->with('success', '商品新增成功!');
}
}
看起來好像沒什麼問題?不,問題可大了。這個 `store` 方法至少違反了「單一職責原則 (Single Responsibility Principle)」五次!它既是驗證器、又是檔案處理器、又是資料庫操作員、還是通知發送員。這會導致:
- 難以測試: 你要怎麼單獨測試「檔案上傳」的邏輯,而不觸發資料庫寫入和 Email 發送?非常困難。
- 難以複用: 如果今天你需要一個 CLI 指令也能新增商品,難道要把這整坨程式碼複製貼上嗎?這絕對是災難的開始。
- 難以維護: 一年後,當需求變更,要修改通知邏輯時,你還記得要來 Controller 裡找嗎?當這裡面的邏輯越來越複雜,每次修改都像在走鋼索。
好了,病得多重我們知道了。接下來,讓我們看看兩位神醫如何出手相救。
傳統的救贖:Repository 模式,資料層的守門人
什麼是 Repository 模式?
Repository 模式(倉儲模式)的核心思想,是建立一個介於「商業邏輯層」和「資料來源層」之間的中介層。它的職責很單純:管理特定模型(Model)的資料查詢與操作。Controller 不需要知道資料是存在 MySQL、PostgreSQL 還是某個外部 API,它只需要跟 Repository 溝通:「嘿,幫我找所有商品」或「幫我新增這筆商品資料」。
實戰演練:用 Repository 模式重構我們的控制器
要實現 Repository 模式,我們通常會搭配 Interface(介面)來做,以達到真正的解耦。這聽起來有點學院派,但相信我,這點「囉嗦」是值得的。
1. 建立 Interface (app/Interfaces/ProductRepositoryInterface.php)
<?php
namespace App\Interfaces;
use App\Models\Product;
interface ProductRepositoryInterface
{
public function getAllProducts();
public function createProduct(array $productDetails): Product;
}
2. 建立 Repository Class (app/Repositories/ProductRepository.php)
<?php
namespace App\Repositories;
use App\Interfaces\ProductRepositoryInterface;
use App\Models\Product;
class ProductRepository implements ProductRepositoryInterface
{
public function getAllProducts()
{
return Product::all();
}
public function createProduct(array $productDetails): Product
{
return Product::create($productDetails);
}
}
3. 綁定 Interface 與 Class (在 app/Providers/RepositoryServiceProvider.php 中,記得要去 config/app.php 註冊這個 Provider)
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Interfaces\ProductRepositoryInterface;
use App\Repositories\ProductRepository;
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(ProductRepositoryInterface::class, ProductRepository::class);
}
}
4. 重構後的 Controller
<?php
namespace App\Http\Controllers\Admin;
// ... 其他 use ...
use App\Interfaces\ProductRepositoryInterface;
use App\Http\Requests\StoreProductRequest; // 將驗證邏輯移出
use App\Services\ProductService; // 商業邏輯移到 Service 層
class ProductController extends Controller
{
private ProductRepositoryInterface $productRepository;
private ProductService $productService;
public function __construct(ProductRepositoryInterface $productRepository, ProductService $productService)
{
$this->productRepository = $productRepository;
$this->productService = $productService;
}
public function store(StoreProductRequest $request)
{
$validatedData = $request->validated();
// 商業邏輯交給 Service 處理
$this->productService->createProductWithNotification($validatedData);
return redirect()->route('admin.products.index')->with('success', '商品新增成功!');
}
}
看到了嗎?Controller 現在乾淨多了!它只負責接收 HTTP Request,呼叫對應的 Service,然後回傳 HTTP Response。資料庫操作的細節被完美封裝在 `ProductRepository` 裡。雖然多了幾個檔案,但每個檔案的職責都非常清晰,這就是分離關注點 (Separation of Concerns) 的威力。
Repository 的小囉嗦時間:它不是萬靈丹
Repository 模式非常強大,尤其在大型、複雜的專案中。但它也有一些被詬病的地方。主要是 Laravel 的 Eloquent ORM 本身就已經是一個非常強大的抽象層了,再包一層 Repository 有時候會讓人覺得是「過度設計」,增加了不必要的複雜度和樣板程式碼 (Boilerplate)。對於簡單的 CRUD 應用,直接在 Controller 或 Service 中使用 Eloquent 也未嘗不可。
新世代的挑戰者:Action 模式,專注於「做一件事」的藝術
什麼是 Action 模式?
如果說 Repository 專注於「資料操作」的抽象化,那 Action 模式則專注於「商業邏輯」的封裝。Action 是一個簡單的 PHP class,它的目標是只做一件事情,並把它做好。例如:「建立一個新商品」、「更新使用者密碼」、「處理訂單結帳」。
這個模式在 Laravel 社群越來越受歡迎,因為它非常直觀,且能完美體現單一職責原則。
實戰演練:用 Action 模式再次進化控制器
讓我們用 Action 模式來處理「建立新商品」這個 use case。
1. 建立 Action Class (app/Actions/Products/CreateNewProductAction.php)
<?php
namespace App\Actions\Products;
use App\Models\Product;
use App\Models\User;
use App\Notifications\ProductCreatedNotification;
use Illuminate\Support\Facades\Storage;
class CreateNewProductAction
{
public function handle(array $data): Product
{
// 1. 檔案上傳
$path = Storage::disk('public')->put('products', $data['image']);
// 2. 建立資料庫紀錄
$product = Product::create([
'name' => $data['name'],
'price' => $data['price'],
'image_path' => $path,
]);
// 3. 觸發通知
$admin = User::find(1);
$admin->notify(new ProductCreatedNotification($product));
return $product;
}
}
2. 重構後的 Controller
<?php
namespace App\Http\Controllers\Admin;
// ... 其他 use ...
use App\Http\Requests\StoreProductRequest;
use App\Actions\Products\CreateNewProductAction;
class ProductController extends Controller
{
public function store(StoreProductRequest $request, CreateNewProductAction $createNewProductAction)
{
// 直接呼叫 Action 處理所有邏輯
$createNewProductAction->handle($request->validated());
return redirect()->route('admin.products.index')->with('success', '商品新增成功!');
}
}
我的天啊,這個 Controller 瘦到只剩三行了!是不是非常優雅?所有的商業邏輯都被封裝在 `CreateNewProductAction` 這個類別中。這個 Action 不僅可以在 Controller 中使用,還可以在 Artisan Command、Queue Job 等任何地方被重複利用,完全符合 DRY (Don’t Repeat Yourself) 原則。
終極對決:Repository vs. Action,我該選哪個?
好了,兩位選手都展示完畢,到底該怎麼選?老實說,這不是二選一的單選題。
- 選擇 Repository 模式的時機:
- 當你的應用程式有非常複雜的資料查詢邏輯(例如:多條件篩選、排序、分頁的報表)。
- 當你預期未來可能需要更換底層的資料來源(例如從 MySQL 換到 MongoDB,雖然不常見但有可能)。
- 當你希望在團隊中強制執行一個統一的資料存取規範時。
- 選擇 Action 模式的時機:
- 當你的應用程式是由明確的「使用案例 (Use Cases)」驅動時。
- 當你希望將商業邏輯從 Controller 中徹底剝離,讓 Controller 保持極度輕薄。
- 對於大多數中小型專案,Action 模式通常更輕量、更直觀。
工程師的貪心之選:我全都要!混合模式
真正的資深工程師,從來不做選擇題。Repository 和 Action 並非互斥,它們可以完美地協同工作!Repository 負責處理「如何」存取資料,而 Action 負責處理「做什麼」商業流程。我們可以把 Repository 注入到 Action 中使用。
<?php
// In app/Actions/Products/CreateNewProductAction.php
class CreateNewProductAction
{
protected $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function handle(array $data): Product
{
// ... 處理檔案上傳 ...
// 使用 Repository 來建立商品
$product = $this->productRepository->createProduct($productDetails);
// ... 處理通知 ...
return $product;
}
}
這樣一來,我們既享受了 Action 模式對商業邏輯的清晰封裝,又享受了 Repository 模式對資料存取的靈活抽象。各司其職,完美!
結論:沒有銀彈,只有最適合的架構
從肥控制器到 Repository,再到 Action,我們看到的是程式碼架構不斷演進、職責不斷細分的過程。記住,寫程式不是為了炫技,而是為了打造一個可維護、可測試、可擴展的系統。無論你選擇哪種模式,或是像我一樣貪心地全都要,核心目標始終不變:讓你的程式碼能優雅地應對未來不斷變化的需求。
希望今天的深度剖析,能幫助你在設計 Laravel Admin 後台架構時,更有信心做出明智的選擇。如果你對 Laravel 架構設計還有更多疑問,或是你的專案正深陷泥沼,需要專業的技術團隊協助你進行架構重構與優化,歡迎隨時與我們浪花科技聯繫。讓我們一起打造更乾淨、更強大的應用程式!
延伸閱讀
- 前後端分離還是大一統?Laravel + Vue / React 整合全攻略,資深工程師帶你選對路
- 還在手刻通知信?Laravel Notifications 終極指南,讓你的應用程式學會「說話」!
- 網站卡住了?別再讓使用者等到天荒地老!Laravel 排程與背景任務 (Scheduler & Queue) 終極指南
對我們的 Laravel 網站客製化開發服務感興趣嗎?歡迎點此與我們聯繫,浪花科技的專業團隊將為您提供最合適的解決方案。
常見問題 (FAQ)
Q1: Repository 模式和 Action 模式最大的差別是什麼?
最主要的差別在於它們關注的「點」不同。Repository 模式專注於「資料存取層的抽象化」,它的目標是讓你的商業邏輯層不需要知道資料是從哪裡來的、怎麼存的。而Action 模式專注於「商業邏輯流程的封裝」,它的目標是將一個完整的使用者操作(Use Case)打包成一個獨立、可重複使用的類別。簡單來說,一個管「資料」,一個管「流程」。
Q2: 我的專案很小,也需要用這些複雜的模式嗎?
這是個好問題!對於非常小的專案或原型開發,直接在 Controller 中使用 Eloquent 處理邏輯是完全可以接受的,這能讓你快速開發。但關鍵在於,當你發現某個 Controller 方法開始變得臃腫、邏輯開始重複出現時,就是一個明確的「重構訊號」。這時候就可以考慮引入 Action 或 Repository 來整理程式碼。先求有,再求好,但心中要有那把衡量「好」的尺。
Q3: 我可以同時使用 Repository 和 Action 模式嗎?
當然可以,而且這是一種非常強大且常見的組合!在這種「混合模式」下,Action 負責編排整個商業邏輯,例如:驗證輸入、處理檔案、觸發事件等。當需要進行資料庫操作時,Action 會呼叫對應的 Repository 來完成。這樣職責劃分非常清晰:Action 是總指揮,Repository 是資料庫專家,兩者合作無間。






