# Mini Shop 專案簡介
[[git]](https://github.com/xd820410/mini-shop)  [[demo]](https://www.huhu543.click/)
---
## 前言
>#### 此專案為若干月前作品,
>#### 仍有許多可精進之處,
>#### 比如:
>#### 1. 測試資料庫從「setUp()強更config」改為「用phpunit.xml寫參數」
>#### 2. API格式使用[API Resource](https://learnku.com/docs/laravel/8.x/eloquent-resources/9410)撰寫
>#### 3. 實測/爬code 看App::call()對於測試mock有無影響,若有影響需改回constructor injection
>#### 等等等……
>#### 因近日較投入於工作,未能挪空修改,還請海涵 (2022-04-26)
---
## 簡述
>#### 採用Repository Pattern,
>#### 將功能細分拆到各Service、Repository的method,
>#### Repository調用Orm寫SQL,
>#### Service調用Repository&處理商務邏輯,
>#### Controller組合各Service整理成所需response,
>#### Middleware處理權限與request。
>#### 部份程式使用[Method injection](https://laravel.com/docs/8.x/container#method-invocation-and-injection),以避免注入過多用不到的class。
## 訪客/會員購物車
>#### 訪客購物車存於session,會員購物車存資料庫,
>#### 用middleware MergeSessionCart()過濾登入後頁面
>#### (↑php artisan ui vue --auth預設登入後轉跳到/home),
>#### 若session cart非空合併至user cart。
## 商品管理
>#### REST api佐以Laravel Sanctum,
>#### 須帶符合權限的Bearer token才可得到response(除取全商品之外)。
>#### 管理頁面以middleware Manager()檢查須有管理員權限才可進入,
>#### 前後端分離,以jQuery ajax串Rest api撰寫。
>![](https://i.imgur.com/cbcCYpU.png)
## 優惠
```=php
//CartController
public function getUserCart(CartService $cartService, DiscountService $discountService)
{
try {
$cart = App::call([new CartService, 'getUserCart'], ['userId' => Auth::user()->id]);
if (!empty($cart)) {
$cart = App::call([new CartService, 'fillItemDataInCart'], ['cart' => $cart]);
$now = Carbon::now();
$nowString = $now->toDateTimeString();
$effectiveDiscount = $discountService->getByDate($nowString);
if (!empty($effectiveDiscount)) {
$cart = $cartService->calculateDiscount($cart, $effectiveDiscount);
}
}
$total = $cartService->calculateTotal($cart);
$returnMessage = [
'result' => 'SUCCESS',
'content' => $cart,
'total' => $total,
];
return response()->json($returnMessage, Response::HTTP_OK);
} catch (Exception $e) {
$errorMessage = [
'result' => 'ERROR',
'message' => $e->getMessage(),
];
return response()->json($errorMessage, Response::HTTP_NOT_FOUND);
}
}
```
>#### 先以getUserCart()取出購物車(僅有商品id與數量),
>#### fillItemDataInCart()將商品資料填進去,
>#### (↑取商品資料不放在迴圈內,先整理購物車所有商品id,再用where in取商品資料,最後組合,避免N+1)
>#### 以Carbon當前時間找出正在進行的優惠,
>#### 最後用calculateDiscount組合購物車與優惠,計算出各商品優惠金額,
>#### 以組合小功能來完成功能。
```=php
//CartService
public function calculateDiscount(Array $cart, $effectiveDiscount)
{
foreach ($effectiveDiscount as $eachDiscount) {
switch ($eachDiscount['type']) {
//某商品滿件折
case Discount::type_single_goods_quantity_threshold:
//some calculation
break;
}
}
return $cart;
}
```
>#### 目前只有一種「[每A件B折](https://github.com/xd820410/mini-shop/blob/master/app/Services/CartService.php#L40)」優惠,
>#### 若須擴充,可再獨立一class,各method各類型優惠計算法,之後注入此method。
## 測試
```=php
//DiscountTest
public function setUp(): void
{
parent::setUp();
config([
'database.connections.mysql.database' => env('DB_TEST_DATABASE', 'test_dbname')
]);
//seed裡面是 9527每兩個8折
Artisan::call('db:seed --class=DiscountTestSeeder');
}
```
```=php
/**
* 檢查優惠區間是否生效(ForUserCart)
* @dataProvider cartForTimeIntervalOfDiscount
* @return void
*/
public function test_timeIntervalForUserCart($rawCart, $goodsData, $now, $expectedTotal)
{
$this->partialMock(CartRepository::class, function (MockInterface $mock) use ($rawCart) {
$userCart = [];
$userCart['payload'] = $rawCart;
$mock->shouldReceive('getByUserId')
->once()
->andReturn($userCart);
});
$this->partialMock(GoodsRepository::class, function (MockInterface $mock) use ($goodsData) {
$mock->shouldReceive('getByIds')
->once()
->andReturn(collect($goodsData));
});
Carbon::setTestNow($now);
//↓It works.
//dump('now: ', Carbon::now());
$this->login();
$response = $this->get('/cart');
$response->assertJson([
'total' => $expectedTotal
], true);
}
```
```=php
/**
* @return array[]
*/
public function cartForTimeIntervalOfDiscount()
{
return [
[
[
'goods_9527' => [
'goods_id' => "9527",
'quantity' => 3,
]
],
[
[
'id' => 9527,
'title' => 'Goods for test',
'description' => null,
'price' => 100,
'image_path' => null
]
],
Carbon::now(),
260
],
[
[
'goods_9527' => [
'goods_id' => "9527",
'quantity' => 3,
]
],
[
[
'id' => 9527,
'title' => 'Goods for test',
'description' => null,
'price' => 100,
'image_path' => null
]
],
new Carbon('2030-01-23 11:53:20'),
300
]
];
}
```
>#### 使用測試資料庫,並插入測試優惠(9527商品每兩件8折),
>#### 以Mockery模擬購物車資料與商品資料(皆為id9527商品),
>#### 最後以Carbon設定測試時間,一組於時間內,一組於時間外,
>#### 測試回傳金額是否如預期。
>#### 另一[test_quantityThreshold()](https://github.com/xd820410/mini-shop/blob/master/tests/Feature/DiscountTest.php#L93)則是雷同的方式,但改為測試商品數量
>#### 「剛好超過一組數量門檻」、「符合三組(一組以上)數量門檻」、「超過三組(一組以上)數量門檻」,
>#### 來測試金額計算是否如預期。