本系列文章已集結成冊與鐵人賽文章差異內容,有以下幾點:
更新至Laravel 8、基礎的PHP重點筆記、加強製作API流程細節、加入程式設計模式,優化、重構程式碼的部分,並且於書籍前面的章節介紹Git。
讓您從製作第一個簡單的API到優化自己的程式碼,分享我的經驗給您,打造自己的最強大腦API,若有興趣的朋友可以參考看看
此篇文章同步發於個人部落格
今天我們繼續拆分程式碼!把商業邏輯的內容寫在 Service
檔案中,這過程中我們都沒有加入新功能,主要是把程式放適當的位置。
Service
檔案 必須手動新增,建立一個 AnimalService
專門來處理動物資源大大小小的商業邏輯。(這裡不包含外部API喔!只用來寫操作動物資源的邏輯!)
假設我今天想把 行政院農委會收容所認養公開資料
的API,接到我的送養平台,要另外寫一個 Service
檔案喔!
我的另外一篇鐵人賽 後端前進PostgreSQL 系列,是在嘗試學習用 PostgreSQL 資料庫!整理、拆解這些農委會的認養公開資料以及PostgreSQL的介紹,有興趣的也可以看一下喔!
Victor: 工商服務時間結束
讀者: 盡然還有廣告時間
app\Services\AnimalService.php
尷尬尷尬,到目前為止程式碼都非常的簡單,要拆開來感覺有點多此一舉,但等到系統變龐大時,一定會感覺出來它的好處,請先相信我!
因為我們 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']]);
}
//...以下略過
}
第五動:修改 AnimalController
中 index
方法的程式
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
方法要完成下列動作
光是一個 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);
}
// 以下略過
}