[PHP] Laravel Controller callAction 應用

by Mesak

前陣子在爬 Laravel 的路由,發現在呼叫 Controller 的時候,並不是直接執行,而是採用 callback function 去呼叫的模式,在路由裡面有一段路徑:

Illuminate\Routing\ControllerDispatcher

    public function dispatch(Route $route, $controller, $method)
    {
        $parameters = $this->resolveClassMethodDependencies(
            $route->parametersWithoutNulls(), $controller, $method
        );

        if (method_exists($controller, 'callAction')) {
            return $controller->callAction($method, $parameters);
        }

        return $controller->{$method}(...array_values($parameters));
    }

可以看到 Controller 在執行之前會先去檢查你的 Controller 有沒有一個叫做 callAtion 的 function 沒有就回傳並呼叫路由指定的 Controller method。

這就引發了我的一個想法,Controller 在實作的時候不管是 API 還是 WEB 路由,都必須要回傳一個 response 類別來顯示內容

其中 API 的回傳,大部分的人都會統一 json,但是內容選擇性相當的多樣,不少人都自訂自己的 JSON Response Schema ,或是利用 Response::macro 的方式去新建一個 API Response。

callAtion 最主要的功能,就是將你的 method 先攔截一次再執行,跟 middleware 作用有點像,但是只給 Controller 專屬利用,這跟我看到有些專案在 Web 裡面先塞入 一些基本頁面參數的數值,這樣省事很多。

API Controller

以我自己的專案,我建立了一個 ApiController,讓路由中 API 的資料夾下的內容全部繼承 ApiController,攔截所有結果,轉換成 API Response 。

之前看過有個方法 Laravel API – The best way to Return the uniform Response for all type of Requests ,裡面直接在每個 API 加上一個 ApiResponseTrait 特徵,讓每個 Controller method 都去呼叫 respondWithResource 這個方法,這樣其實一開始很方便,但是當你維護的程式碼一龐大,就很恐怖了。

假設我們先以 api 版本 做為開發設計,JSON Response Schema 都需要訂一個版本,可以給 手機一個版本,給 Web 一個版本,或是版本更新的問題,以這樣的前提之下去設計,同一維護 JSON Response Schema 就很重要,因此 ApiController callAtion 就可以很好的重複利用,你可以在每個路由建立不同的 ApiController ,呼叫自訂的 JSON Response Schema ,讓 Controller 去專注的回傳 Resource 或是 Collection :

    public function callAction($method, $parameters)
    {
        $result =  $this->{$method}(...array_values($parameters));
        $httpStatus = ($method  === 'store') ? 201 : 200;
        if ($result instanceof ResponseSchema) {
            $response = &$result;
        } else {
            $response = new ResponseSchema($result);
        }
        $response->setStatus($httpStatus);
        return response()->json($response, $httpStatus);
    }

我建立了一個 ResponseSchema 的類別,專門定義 JSON Schema 格式,然後統一回傳,Controller 回傳的內容都會是 執行成功後的結果,唯一有一點小差異的是 新增的method 統一我叫做 store ,會把 http status code 回傳 201。

由於這邊只回傳 成功的資訊,所以 Controller 就不用寫一堆 try catch,專注的把必要的資訊回傳就可以了

Error Handler 錯誤處理

錯誤處理該如何處理呢,根據官方網站所提到 the-exception-handler,錯誤有專門的錯誤處理 Handler ,所以只需要在

App\Exceptions\Handler

這支程式裡面專注的去處理錯誤,在 render function 裡面去載入 ResponseSchema 吐出相應的方法,就可以統一 JSON Response Schema

    public function render($request, Throwable $exception)
    {
        $responseSchema = new ResponseSchema();
        $statusCode = 500;
        $responseSchema->setException($exception);
        $responseSchema->setMessage('Server Error');
        $responseSchema->setStatus($statusCode);
        if ($request->is(['api/*', 'oauth/*'])) {
            $request->headers->set('Accept', 'application/json');
            return response($responseSchema->toArray(), $statusCode);
        }
        return parent::render($request, $exception);
    }

目前這方法是我早期實作 API Response 的方法,這樣可以專注在 Controller 寫作上,達到分層分工的目的

目前專案透過這種 API 實作後的內容結構都長這樣:

因為錯誤都會丟到 Exceptions 去處理,所以也不用處理太多判斷邏輯,Request 近來之前就可以擋掉不少錯誤,取出模組的時候也可以用 findOrFail 去處理,錯誤就會直接拋錯,最後回傳 Resource 就搞定

You may also like