iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 26
2
Modern Web

使用 Laravel 打造 RESTful API系列 第 26

讓你的程式更美好 - Service後可以加入很多功能

  • 分享至 

  • xImage
  •  

使用Laravel 8 PHP主流框架打造RESTful API(iT邦幫忙鐵人賽系列書)ISBN:9789864345304

本系列文章已集結成冊與鐵人賽文章差異內容,有以下幾點:

更新至Laravel 8、基礎的PHP重點筆記、加強製作API流程細節、加入程式設計模式,優化、重構程式碼的部分,並且於書籍前面的章節介紹Git。

讓您從製作第一個簡單的API到優化自己的程式碼,分享我的經驗給您,打造自己的最強大腦API,若有興趣的朋友可以參考看看

天瓏網路書局:
https://www.tenlong.com.tw/products/9789864345304


此篇文章同步發於個人部落格

今天我們繼續拆分程式碼!把商業邏輯的內容寫在 Service 檔案中,這過程中我們都沒有加入新功能,主要是把程式放適當的位置。

Service

Service檔案 必須手動新增,建立一個 AnimalService 專門來處理動物資源大大小小的商業邏輯。(這裡不包含外部API喔!只用來寫操作動物資源的邏輯!)

假設我今天想把 行政院農委會收容所認養公開資料 的API,接到我的送養平台,要另外寫一個 Service檔案喔!

我的另外一篇鐵人賽 後端前進PostgreSQL 系列,是在嘗試學習用 PostgreSQL 資料庫!整理、拆解這些農委會的認養公開資料以及PostgreSQL的介紹,有興趣的也可以看一下喔!

Victor: 工商服務時間結束 /images/emoticon/emoticon01.gif
讀者: 盡然還有廣告時間 /images/emoticon/emoticon04.gif

新建檔案

app\Services\AnimalService.php

Controller 減肥開始

尷尬尷尬,到目前為止程式碼都非常的簡單,要拆開來感覺有點多此一舉,但等到系統變龐大時,一定會感覺出來它的好處,請先相信我!

因為我們 AnimalController 程式碼最長的就是 index 方法,所以我們把篩選條件排序邏輯,拆分到 Service檔案中,雖然我們寫的篩選、排序邏輯很通用,沒有客製化邏輯,就只是欄位跟後面的數值去篩選及排序,如下所示,但真實情況下,可能會有一些客製化的邏輯!所以我們把這兩個部分拆開放在 AnimalService.php

app/Http/Controllers/AnimalController.php 原本撰寫查詢動物列表的程式碼

public function index(Request $request)
{
    // 設定預設值

    $marker = isset($request->marker) ? $request->marker : 1;
    $limit = isset($request->limit) ? $request->limit : 10;

    $query = Animal::query();

    // 篩選欄位條件 Day26 拆到Service
    if (isset($request->filters)) {
        $filters = explode(',', $request->filters);
        foreach ($filters as $key => $filter) {
            list($criteria, $value) = explode(':', $filter);
            $query->where($criteria, 'like', "%$value%");
        }
    }

    //排列順序 Day26 拆到Service
    if (isset($request->sort)) {
        $sorts = explode(',', $request->sort);
        foreach ($sorts as $key => $sort) {
            list($criteria, $value) = explode(':', $sort);
            if ($value == 'asc' || $value == 'desc') {
                $query->orderBy($criteria, $value);
            }
        }
    } else {
        $query->orderBy('id', 'asc');
    }

    $animals = $query->where('id', '>=', $marker)->paginate($limit);

    return response($animals, Response::HTTP_OK);
}

拆解步驟

打開 app/Services/AnimalService.php 先來拆篩選邏輯

第一動:為方法取一個好名字 filterAnimals 這邊命名方式的建議,我是把持著幾個簡單的原則,就是 駝峰格式動作+資源名稱簡易單字不隨便縮寫

第二動:先把篩選方法複製過來

<?php

namespace App\Services;

class AnimalService
{
    public function filterAnimals(Request $request)
    {

        // 篩選欄位條件
        if (isset($request->filters)) {
            $filters = explode(',', $request->filters);
            foreach ($filters as $key => $filter) {
                list($criteria, $value) = explode(':', $filter);
                $query->where($criteria, 'like', "%$value%");
            }
        }
    }
}

第三動:上面的程式碼需要修改,這樣還不能動,修改結果如下所示(可以上下比對一下)

<?php

namespace App\Services;

class AnimalService
{
    public function filterAnimals($filters, $query)
    {
        // 篩選欄位條件
        if (isset($filters)) {
            $filtersArray = explode(',', $filters);
            foreach ($filtersArray as $key => $filter) {
                list($criteria, $value) = explode(':', $filter);
                $query->where($criteria, $value);
            }
        }
        return $query;
    }
}

第四動:來到 AnimalController 依賴注入 AnimalService

//記得引入 AnimalService
use App\Services\AnimalService;

class AnimalController extends Controller
{
    // class 加入以下程式碼
    private $animalService;

    public function __construct(AnimalService $animalService)
    {
        $this->animalService = $animalService;
        $this->middleware('auth:api', ['except' => ['index','show']]);
    }

    //...以下略過
}

第五動:修改 AnimalControllerindex 方法的程式

public function index(Request $request)
{
    // 設定預設值

    $marker = isset($request->marker) ? $request->marker : 1;
    $limit = isset($request->limit) ? $request->limit : 10;

    $query = Animal::query();

    // 加入下面這一行
    $query = $this->animalService->filterAnimals($request->filters, $query);
    
    // 篩選欄位條件(刪除)
    // if (isset($request->filters)) {
    //     $filters = explode(',', $request->filters);
    //     foreach ($filters as $key => $filter) {
    //         list($criteria, $value) = explode(':', $filter);
    //         $query->where($criteria, 'like', "%$value%");
    //     }
    // }

    //排列順序
    if (isset($request->sort)) {
        $sorts = explode(',', $request->sort);
        foreach ($sorts as $key => $sort) {
            list($criteria, $value) = explode(':', $sort);
            if ($value == 'asc' || $value == 'desc') {
                $query->orderBy($criteria, $value);
            }
        }
    } else {
        $query->orderBy('id', 'asc');
    }

    $animals = $query->where('id', '>=', $marker)->paginate($limit);

    return response($animals, Response::HTTP_OK);
}

第六動:儲存測試一下! 成功通過測試程式!但別忘記我們撰寫的測試很少,尤其是測試查詢動物的程式碼,只有測試回傳的資料結構而已!所以建議手動送送看請求,確認篩選的部分是否正確喔!

測試成功

另外一個排序邏輯的拆分保留給您小試身手一下吧! 我最後會把這兩個檔案貼到今天文章的附件單元。


可以偷偷去看一下文章最下面的 Controller 是不是比較簡單明瞭了?!控制要分配給哪個Service檔案,邏輯細節的部分交由Service拆分的方法執行,可能目前程式碼沒有很多體會不太出來,但如果系統越來越大,會越有感覺喔!

題外話:如果要呼叫同一個檔案的其他方法,可以這麼做!
例如 在 AnimalService 檔案中 filterAnimals() 方法外,呼叫 filterAnimals() 的話 $this->filterAnimals(這裡給對應的參數就可以囉)

假設功能需求是這樣!

業主:我想要讓會員可以刊登動物資料,至少一定要有圖片、影片,而且要記錄動物有沒有結紮,這些操作希望都可以有紀錄,並且想要讓關注的人收到通知!

翻譯蒟蒻

讓會員刊登送養動物資料必須檢查資料是否符合我們的資料格式上傳圖片或影片,經由系統處理完成以後寄送一封Email給刊登資料會員,通知會員刊登成功,並且記錄一筆記錄到系統,請且通知每個關注追蹤這位會員的人,Email告知追蹤對象有刊登動物資訊。

分析這個需求

需求分析我們以動作討論為主,資料庫規劃這裡先不談

Controller 需要做到這些部分,會員操作 POST /api/animal 的動作 對應的 store 方法要完成下列動作

  1. 檢查資料格式
  2. 檔案圖片影片上傳到伺服器,執行剪裁、製造縮圖,或影片轉檔。(可能有隊列處理)
  3. 資料寫入資料庫
  4. 傳送Email給刊登會員 (通知刊登起訖日,注意事項...)
  5. 寫入新增動物資料的操作記錄
  6. 檢查追蹤這位會員的其他會員
  7. 發送通知,不管是Email 或是網站的通知 app的推播...
  8. 轉換輸出格式,回傳新建資料!

光是一個 POST /api/animal 動作,全部的邏輯都寫在controller 絕對是一個不好的決定。

最好可以把它拆開來一個動作一個方法甚至可以想辦法拆成可以重複利用的多個方法,可用在不同地方!

附件

附上拆分程式碼!

app/Services/AnimalService.php

<?php

namespace App\Services;

class AnimalService
{
    /**
     * 
     */
    public function filterAnimals($filters, $query)
    {

        // 篩選欄位條件
        if (isset($filters)) {
            $filtersArray = explode(',', $filters);
            foreach ($filtersArray as $key => $filter) {
                list($criteria, $value) = explode(':', $filter);
                $query->where($criteria, $value);
            }
        }

        return $query;
    }
    
    /**
     * 
     */
    public function sortAnimals($sorts, $query)
    {
        if (isset($sorts)) {
            $sortArray = explode(',', $sorts);
            foreach ($sortArray as $key => $sort) {
                list($criteria, $value) = explode(':', $sort);
                if ($value == 'asc' || $value == 'desc') {
                    $query->orderBy($criteria, $value);
                }
            }
        } else {
            $query->orderBy('id', 'asc');
        }

        return $query;
    }
}

app/Http/Controllers/AnimalController.php

<?php

namespace App\Http\Controllers;

use App\Animal;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Http\Resources\AnimalResource;
use Auth;
use App\Http\Requests\StoreAnimalRequest;
use App\Services\AnimalService;

class AnimalController extends Controller
{
    private $animalService;

    public function __construct(AnimalService $animalService)
    {
        $this->animalService = $animalService;
        $this->middleware('auth:api', ['except' => ['index','show']]);
    }

    /**
     * Display a listing of the resource.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        // 設定預設值

        $marker = isset($request->marker) ? $request->marker : 1;
        $limit = isset($request->limit) ? $request->limit : 10;

        $query = Animal::query();

        $query = $this->animalService->filterAnimals($request->filters, $query);
        
        $query = $this->animalService->sortAnimals($request->sort, $query);

        $animals = $query->where('id', '>=', $marker)->paginate($limit);

        return response($animals, Response::HTTP_OK);
    }

    // 以下略過

}

上一篇
讓你的程式更美好 - 重構現有的程式碼
下一篇
更好的自己更好的 API
系列文
使用 Laravel 打造 RESTful API30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言