肥 Controller 瘦不下來?Laravel 後台架構終極對決:Repository vs. Action 模式,資深工程師帶你選對屠龍刀!

2025/08/15 | Laravel技術分享

肥 Controller 瘦不下來?Laravel 後台架構終極對決:Repository vs. Action 模式,資深工程師帶你選對屠龍刀!

嗨,我是浪花科技的 Eric。身為一個整天跟程式碼打交道的工程師,我看過太多令人頭皮發麻的 Laravel Controller。一個 `store` 方法洋洋灑灑寫了兩百行,裡面混雜著資料驗證、檔案上傳、商業邏輯、資料庫操作,甚至還順便發了幾封 Email 通知。每次要修改一小塊功能,都得像拆炸彈一樣小心翼翼,深怕動到不該動的地方,然後整個系統就跟你鬧脾氣,直接掛掉。

這就是所謂的「肥控制器 (Fat Controller)」,是專案走向維護地獄的特快車。今天,我不是要來抱怨的(好吧,可能有一點),而是要帶大家深入探討兩種在 Laravel 社群中廣受討論,用來幫 Controller 瘦身的強大設計模式:Repository PatternAction 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 網站客製化開發服務感興趣嗎?歡迎點此與我們聯繫,浪花科技的專業團隊將為您提供最合適的解決方案。

常見問題 (FAQ)

Q1: Repository 模式和 Action 模式最大的差別是什麼?

最主要的差別在於它們關注的「點」不同。Repository 模式專注於「資料存取層的抽象化」,它的目標是讓你的商業邏輯層不需要知道資料是從哪裡來的、怎麼存的。而Action 模式專注於「商業邏輯流程的封裝」,它的目標是將一個完整的使用者操作(Use Case)打包成一個獨立、可重複使用的類別。簡單來說,一個管「資料」,一個管「流程」。

Q2: 我的專案很小,也需要用這些複雜的模式嗎?

這是個好問題!對於非常小的專案或原型開發,直接在 Controller 中使用 Eloquent 處理邏輯是完全可以接受的,這能讓你快速開發。但關鍵在於,當你發現某個 Controller 方法開始變得臃腫、邏輯開始重複出現時,就是一個明確的「重構訊號」。這時候就可以考慮引入 Action 或 Repository 來整理程式碼。先求有,再求好,但心中要有那把衡量「好」的尺。

Q3: 我可以同時使用 Repository 和 Action 模式嗎?

當然可以,而且這是一種非常強大且常見的組合!在這種「混合模式」下,Action 負責編排整個商業邏輯,例如:驗證輸入、處理檔案、觸發事件等。當需要進行資料庫操作時,Action 會呼叫對應的 Repository 來完成。這樣職責劃分非常清晰:Action 是總指揮,Repository 是資料庫專家,兩者合作無間。

 
立即諮詢,索取免費1年網站保固