Laravel 學習筆記

# 前言

本篇為 Laravel 的學習筆記, 主要將看到的, 學到的技術轉換成 Q&A 的方式以加速學習



# Production 優化

# PHP

php.ini 文件

php.ini 中的 memory_limit 用於設定單個 PHP process 可以使用的系統內存最大值


# Laravel timezone 與 MySQL timezone

# 當 Laravel 收到沒有時區的時間

  • 預設此時間為 Laravel timezone, 不另外做轉換直接帶給資料庫, 如下圖:
    sequenceDiagram
    participant Client
    participant Laravel
    participant MySQL
    NOTE OVER Client,Laravel: 假設 Laravel 時區為 Asia/Taipei
    Client-->>Laravel: 給你 2021-02-05 15:27:46
    NOTE OVER Client,Laravel: Laravel 會默認上述時間為 Asia/Taipei
    Laravel-->>MySQL: 給你 2021-02-05 15:27:46

# 當 Laravel 收到有時區的時間

  • 將時間轉為 Laravel 時區, 如下圖:

    sequenceDiagram
    participant Client
    participant Laravel
    participant MySQL
    NOTE OVER Client,Laravel: 假設 Laravel 時區為 Asia/Taipei
    Client-->>Laravel: 給你 2021-02-05T15:27:46+0400
    NOTE OVER Client,Laravel: Laravel 會將上面的時間轉為 Asia/Taipei
    NOTE OVER Client,Laravel: 即 2021-02-05T19:27:46+0800
    Laravel-->>MySQL: 給你 2021-02-05 19:27:46

    # Laravel 回傳時間

    sequenceDiagram
    participant Client
    participant Laravel
    participant MySQL
    NOTE OVER Client,Laravel: 假設 Laravel 時區為 Asia/Taipei
    MySQL-->>Laravel: 給你 2021-02-05 19:27:46
    NOTE OVER Client,Laravel: Laravel 默認上面時間的時區為 Asia/Taipei
    NOTE OVER Client,Laravel: 即 2021-02-05T11:27:46+0000 (預設回傳 ISO8601)
    Laravel-->>Client: 給你 2021-02-05T11:27:46+0000

# 當 MySQL 的 column 為 datetime

sequenceDiagram
participant Laravel
participant MySQL
Laravel-->>MySQL: 給你 2021-02-05 19:27:46
NOTE OVER Laravel,MySQL: 當 column type 為 datetime, 不做任何變更
NOTE OVER Laravel,MySQL: 實存 2021-02-05 19:27:46 到資料庫

# 當 MySQL 的 column 為 timestamp

sequenceDiagram
participant Laravel
participant MySQL
Laravel-->>MySQL: 給你 2021-02-05 19:27:46
NOTE OVER Laravel,MySQL: MySQL column type 為 timestamp, 假設 MySQL timezone 為 Asia/Taipei
NOTE OVER Laravel,MySQL: 使用 default timezone 將該時間轉為 UTC 儲存
NOTE OVER Laravel,MySQL: 實存 2021-02-05T11:27:46+0000, 顯示 2021-02-05 19:27:46
NOTE OVER Laravel,MySQL: 之後若是更換時區, 會以 2021-02-05T11:27:46+0000 來轉換為該時區時間顯示

# 調整 MySQL timezone 對 Laravel 輸出的影響 (datetime)

sequenceDiagram
participant Laravel
participant MySQL
NOTE OVER Laravel,MySQL: 時區為 Asia/Taipei
Laravel-->>MySQL: 給你 2021-02-05 19:27:46
NOTE OVER Laravel,MySQL: 實存 2021-02-05 19:27:46
NOTE OVER Laravel,MySQL: 修改時區為 UTC
MySQL->>Laravel: 給你 2021-02-05 19:27:46
NOTE OVER Laravel,MySQL: datetime 不受更換時區影響

# 調整 MySQL timezone 對 Laravel 輸出的影響 (timestamp)

sequenceDiagram
participant Laravel
participant MySQL
NOTE OVER Laravel,MySQL: 時區為 Asia/Taipei
Laravel-->>MySQL: 給你 2021-02-05 19:27:46
NOTE OVER Laravel,MySQL: 實存 2021-02-05T11:27:46+0000
NOTE OVER Laravel,MySQL: 修改時區為 UTC
MySQL->>Laravel: 給你 2021-02-05 11:27:46
NOTE OVER Laravel,MySQL: timestamp 會因為修改時區而回傳不一樣的 datetime string

# Questions and Answers

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    use App\Exceptions\InternalException;

    protected function refundInstallmentItem(InstallmentItem $item)
    {
    $refundNo = $this->order->refund_no.'_'.$item->sequence;
    switch ($item->payment_method) {
    case 'wechat':
    app('wechat_pay')->refund([
    'transaction_id' => $item->payment_no,
    'total_fee' => $item->total * 100,
    'refund_fee' => $item->base * 100,
    'out_refund_no' => $refundNo,
    'notify_url' => '' // todo,
    ]);
    $item->update([
    'refund_status' => InstallmentItem::REFUND_STATUS_PROCESSING,
    ]);
    break;
    case 'alipay':
    $ret = app('alipay')->refund([
    'trade_no' => $item->payment_no,
    'refund_amount' => $item->base,
    'out_request_no' => $refundNo,
    ]);
    if ($ret->sub_code) {
    $item->update([
    'refund_status' => InstallmentItem::REFUND_STATUS_FAILED,
    ]);
    } else {
    $item->update([
    'refund_status' => InstallmentItem::REFUND_STATUS_SUCCESS,
    ]);
    }
    break;
    default:
    throw new InternalException('未知订单支付方式:'.$item->payment_method);
    break;
    }
    }
    }
  • Answer:

    <?php
    use App\Exceptions\InternalException;

    protected function refundInstallmentItem(InstallmentItem $item)
    {
    // 退款单号使用商品订单的退款号与当前还款计划的序号拼接而成
    $refundNo = $this->order->refund_no.'_'.$item->sequence;
    // 根据还款计划的支付方式执行对应的退款逻辑
    switch ($item->payment_method) {
    case 'wechat':
    app('wechat_pay')->refund([
    'transaction_id' => $item->payment_no, // 这里我们使用微信订单号来退款
    'total_fee' => $item->total * 100, //原订单金额,单位分
    'refund_fee' => $item->base * 100, // 要退款的订单金额,单位分,分期付款的退款只退本金
    'out_refund_no' => $refundNo, // 退款订单号
    // 微信支付的退款结果并不是实时返回的,而是通过退款回调来通知,因此这里需要配上退款回调接口地址
    'notify_url' => '' // todo,
    ]);
    // 将还款计划退款状态改成退款中
    $item->update([
    'refund_status' => InstallmentItem::REFUND_STATUS_PROCESSING,
    ]);
    break;
    case 'alipay':
    $ret = app('alipay')->refund([
    'trade_no' => $item->payment_no, // 使用支付宝交易号来退款
    'refund_amount' => $item->base, // 退款金额,单位元,只退回本金
    'out_request_no' => $refundNo, // 退款订单号
    ]);
    // 根据支付宝的文档,如果返回值里有 sub_code 字段说明退款失败
    if ($ret->sub_code) {
    $item->update([
    'refund_status' => InstallmentItem::REFUND_STATUS_FAILED,
    ]);
    } else {
    // 将订单的退款状态标记为退款成功并保存退款订单号
    $item->update([
    'refund_status' => InstallmentItem::REFUND_STATUS_SUCCESS,
    ]);
    }
    break;
    default:
    // 原则上不可能出现,这个只是为了代码健壮性
    throw new InternalException('未知订单支付方式:'.$item->payment_method);
    break;
    }
    }
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    namespace App\Jobs;

    class RefundInstallmentOrder implements ShouldQueue
    {
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $order;

    public function __construct(Order $order)
    {
    $this->order = $order;
    }

    public function handle()
    {
    if ($this->order->payment_method !== 'installment'
    || !$this->order->paid_at
    || $this->order->refund_status !== Order::REFUND_STATUS_PROCESSING) {
    return;
    }
    if (!$installment = Installment::query()->where('order_id', $this->order->id)->first()) {
    return;
    }
    foreach ($installment->items as $item) {
    if (!$item->paid_at || in_array($item->refund_status, [
    InstallmentItem::REFUND_STATUS_SUCCESS,
    InstallmentItem::REFUND_STATUS_PROCESSING,
    ])) {
    continue;
    }

    try {
    $this->refundInstallmentItem($item);
    } catch (\Exception $e) {
    \Log::warning('分期退款失败:'.$e->getMessage(), [
    'installment_item_id' => $item->id,
    ]);

    continue;
    }
    }
    $allSuccess = true;
    foreach ($installment->items as $item) {
    if ($item->paid_at &&
    $item->refund_status !== InstallmentItem::REFUND_STATUS_SUCCESS) {
    $allSuccess = false;
    break;
    }
    }

    if ($allSuccess) {
    $this->order->update([
    'refund_status' => Order::REFUND_STATUS_SUCCESS,
    ]);
    }
    }

    protected function refundInstallmentItem(InstallmentItem $item)
    {
    // todo
    }
    }
  • Answer:

    <?php
    namespace App\Jobs;

    class RefundInstallmentOrder implements ShouldQueue
    {
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $order;

    public function __construct(Order $order)
    {
    $this->order = $order;
    }

    public function handle()
    {
    // 如果商品订单支付方式不是分期付款、订单未支付、订单退款状态不是退款中,则不执行后面的逻辑
    if ($this->order->payment_method !== 'installment'
    || !$this->order->paid_at
    || $this->order->refund_status !== Order::REFUND_STATUS_PROCESSING) {
    return;
    }
    // 找不到对应的分期付款,原则上不可能出现这种情况,这里的判断只是增加代码健壮性
    if (!$installment = Installment::query()->where('order_id', $this->order->id)->first()) {
    return;
    }
    // 遍历对应分期付款的所有还款计划
    foreach ($installment->items as $item) {
    // 如果还款计划未支付,或者退款状态为退款成功或退款中,则跳过
    if (!$item->paid_at || in_array($item->refund_status, [
    InstallmentItem::REFUND_STATUS_SUCCESS,
    InstallmentItem::REFUND_STATUS_PROCESSING,
    ])) {
    continue;
    }
    // 调用具体的退款逻辑,
    try {
    $this->refundInstallmentItem($item);
    } catch (\Exception $e) {
    \Log::warning('分期退款失败:'.$e->getMessage(), [
    'installment_item_id' => $item->id,
    ]);
    // 假如某个还款计划退款报错了,则暂时跳过,继续处理下一个还款计划的退款
    continue;
    }
    }
    // 设定一个全部退款成功的标志位
    $allSuccess = true;
    // 再次遍历所有还款计划
    foreach ($installment->items as $item) {
    // 如果该还款计划已经还款,但退款状态不是成功
    if ($item->paid_at &&
    $item->refund_status !== InstallmentItem::REFUND_STATUS_SUCCESS) {
    // 则将标志位记为 false
    $allSuccess = false;
    break;
    }
    }
    // 如果所有退款都成功,则将对应商品订单的退款状态修改为退款成功
    if ($allSuccess) {
    $this->order->update([
    'refund_status' => Order::REFUND_STATUS_SUCCESS,
    ]);
    }
    }

    protected function refundInstallmentItem(InstallmentItem $item)
    {
    // todo
    }
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    $mock = \Mockery::mock('CaptainsConsole');
    $mock->shouldReceive('foo->bar->zebra->alpha->selfDestruct')->andReturn('Ten!');
  • Answer:
    當一個 object 會呼叫多個 method chain 時, 可帶入整個 chain, 並定義 return value, mockery 會忽略中間所有被呼叫的 method

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    class Model
    {
    public function test(&$data)
    {
    return $this->doTest($data);
    }

    protected function doTest(&$data)
    {
    $data['something'] = 'wrong';
    return $this;
    }
    }

    class Test extends \PHPUnit\Framework\TestCase
    {
    public function testModel()
    {
    $mock = \Mockery::mock('Model[test]')->shouldAllowMockingProtectedMethods();

    $mock->shouldReceive('test')
    ->with(\Mockery::on(function(&$data) {
    $data['something'] = 'wrong';
    return true;
    }));

    $data = array('foo' => 'bar');

    $mock->test($data);
    $this->assertTrue(isset($data['something']));
    $this->assertEquals('wrong', $data['something']);
    }
    }
  • Answer:
    通常比較少遇到這種案例, 帶入 reference parameter 到 protected method, 可 mock 上一層的 public method with shouldAllowMockingProtectedMethods(), 這個 public method 會被當作是 protected method 的 proxy

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    namespace App\Console\Commands\Cron;

    class CalculateInstallmentFine extends Command
    {
    protected $signature = 'cron:calculate-installment-fine';

    protected $description = '计算分期付款逾期费';

    public function handle()
    {
    InstallmentItem::query()
    // 预加载分期付款数据,避免 N + 1 问题
    ->with(['installment'])
    ->whereHas('installment', function ($query) {
    // 对应的分期状态为还款中
    $query->where('status', Installment::STATUS_REPAYING);
    })
    // 还款截止日期在当前时间之前
    ->where('due_date', '<=', Carbon::now())
    // 尚未还款
    ->whereNull('paid_at')
    // 使用 chunkById 避免一次性查询太多记录
    ->chunkById(1000, function ($items) {
    // 遍历查询出来的还款计划
    foreach ($items as $item) {
    // 通过 Carbon 对象的 diffInDays 直接得到逾期天数
    $overdueDays = Carbon::now()->diffInDays($item->due_date);
    // 本金与手续费之和
    $base = big_number($item->base)->add($item->fee)->getValue();
    // 计算逾期费
    $fine = big_number($base)
    ->multiply($overdueDays)
    ->multiply($item->installment->fine_rate)
    ->divide(100)
    ->getValue();
    // 避免逾期费高于本金与手续费之和,使用 compareTo 方法来判断
    // 如果 $fine 大于 $base,则 compareTo 会返回 1,相等返回 0,小于返回 -1
    $fine = big_number($fine)->compareTo($base) === 1 ? $base : $fine;
    $item->update([
    'fine' => $fine,
    ]);
    }
    });
    }
    }
  • Answer:
    跑 cron 例行檢查逾期款項, 並算出逾期金額

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function testCanOverrideExpectedParametersOfInternalPHPClassesToPreserveRefs()
    {
    \Mockery::getConfiguration()->setInternalClassMethodParamMap(
    'MongoCollection',
    'insert',
    array('&$data', '$options = array()')
    );
    $m = \Mockery::mock('MongoCollection');
    $m->shouldReceive('insert')->with(
    \Mockery::on(function(&$data) {
    if (!is_array($data)) return false;
    $data['_id'] = 123;
    return true;
    }),
    \Mockery::any()
    );

    $data = array('a'=>1,'b'=>2);
    $m->insert($data);

    $this->assertTrue(isset($data['_id']));
    $this->assertEquals(123, $data['_id']);

    \Mockery::resetContainer();
    }
  • Answer:
    一般來說, mockery 可以 mock call by reference, 但對 internal method 不起作用。 可以使用 setInternalClassMethodParamMap() 來 map class, method, 以及 args

Laravel 當中, 以下的語法代表什麼意思?

列出 $users query builder 的 query 語法以及帶入的變數

<?php
dd($users->toSql(), $users->getBindings());
Laravel 當中, 以下代碼代表什麼意思?

$user->address 可獲得時, 取值, 不可獲得時, 回傳 null

<?php
return optional($user->address)->street;
以下的 Laravel 程式碼代表什麼意思?
<?php
$users = User::merchant()
->with('owner')
->when($q, function ($query, $q) {
return $query->where('username', 'LIKE', '%'.$q.'%')
->orWhere('name', 'LIKE', '%'.$q.'%')
->orWhere('email', 'LIKE', '%'.$q.'%');
});

public function scopeMerchant(Builder $query)
{
return $query->where('role_id', Role::MERCHANT);
}

取得 User model 的 merchant scope, eager load owner relation, 當 $q 不為 null, 執行 closure, 接上 closure 中的 query builder, 在 username, name, email 三個欄位中模糊搜尋 $q

以下的 Laravel 程式碼的 appends 邏輯是什麼? 在 Laravel 的分頁模式中, 若未將全部來自於前端的 query 帶入, 那分頁的 url 將會缺少必要的 query, 等於只有第一頁會有 帶入的 query 結果
<?php
return new UserCollection($users->paginate($row)->appends(request()->query->all()));
以下的 Laravel 程式碼中的 latest 代表什麼意思? 以 id 排序
<?php
$transactions = auth()->user()->transactions()
->whereBetween('created_at', [$startedAt, $endedAt])
->latest('id')
->with('wallet')
->paginate()
->appends($request->query->all());
以下的 Laravel 程式碼中的 fill 代表什麼意思? 將值注入 userBankCard model, 帶入參數可以是一個 array
<?php
$userBankCard->fill(
$request->only(
'card_holder_name', 'card_number', 'bank_name'
)
);
以下的 Laravel 程式碼中, 為什麼要使用 collect function? 這樣如果前端帶錯, 帶成 string 的話, 會先將 string 轉成 collection, 再轉成 array
<?php
$userBankCards = UserBankCard::when(request()->q, function ($query, $q) {
$query->where(function ($query) use ($q) {
$query->where('card_holder_name', 'LIKE', '%' . $q . '%')
->orWhere('card_number', 'LIKE', '%' . $q . '%')
->orWhere('bank_name', 'LIKE', "%$q%");
});
})
->when(request()->status, function ($query, $status) {
$query->whereIn('status', collect($status)->toArray());
})
->where('user_id', auth()->user()->getKey())
->paginate($row)
->appends(request()->query->all());
以下的 Laravel 程式碼代表什麼意思?
<?php
$depositStats = Deposit::whereBetween('created_at', [$startDate, $endDate])->where('status', Deposit::STATUS_SUCCESS)->groupBy('system_bank_card_type')->get([
'system_bank_card_type',
DB::raw('SUM(amount) AS total_amount, SUM(fee) AS total_fee, COUNT(id) AS total_count')
])->keyBy('system_bank_card_type');
取得 Deposit model, 以帶入日期過濾, 以 SUCCESS status 過濾, 以 system_bank_card_type 分類, 在取得四個值, 分別是 system_bank_card_type, total_amount, total_fee, total_count, 若照預設, 會是一個 collection 裡面有兩個 model, index 為 0 跟 1, 使用 keyBy 來將 0 跟 1 依照 system_bank_card_type 做區分, 所以會變成一個 collection 裡頭有兩個 model, 以 system_bank_card_type 做區分
以下的 Laravel 程式碼為什麼要使用 first? 因為該 query 撈出來後, 只會有一筆 model, 如果是用 get 的話, 會是一個 collection 裡有一個 model, 所以直接使用 first() 即可
<?php
$withdrawStat = Withdraw::whereBetween('created_at', [$startDate, $endDate])
->where('status', Withdraw::STATUS_SUCCESS)
->first([
DB::raw('SUM(amount) AS total_amount, SUM(fee) AS total_fee, COUNT(id) AS total_count')
]);
以下的 Laravel 程式碼中, keyBy 的用途是?

如果不使用 keyBy 的話, 正常來說一個 collection 裡頭有多個 model 會以默認 index, 0, 1, 2 …, keyBy 可以使用指定的 key 來給 model 分組, 在這個例子中, 就是以 model 下的 slug 欄位的值做分組

<?php
$wallets = auth()->user()->wallets()->get()->keyBy('slug');
以下的 Laravel 程式碼中, data_get 的用途是?

$depositStats 結構像是這樣 $depositStats = ['BankCard::TYPE_FEE' => ['total_count', 'total_amount', 'total_count', 'total_amount']], data_get 可以取得一個 collection 裡頭的巢狀 array 值

<?php
return response()->json([
'data' => [
'fee_wallet_deposit_success_count' => data_get($depositStats, [BankCard::TYPE_FEE, 'total_count'], 0),
'fee_wallet_deposit_success_amount' => data_get($depositStats, [BankCard::TYPE_FEE, 'total_amount'], 0) / 100, // todo remove hard code
'withdraw_wallet_deposit_success_count' => data_get($depositStats, [BankCard::TYPE_WITHDRAW, 'total_count'], 0),
'withdraw_wallet_deposit_success_amount' => data_get($depositStats, [BankCard::TYPE_WITHDRAW, 'total_amount'], 0) / 100, // todo remove hard code
'withdraw_success_count' => $withdrawStat->total_count ?? 0,
'withdraw_success_amount' => ($withdrawStat->total_amount ?? 0) / 100, // todo remove hard code
'fee_wallet_balance' => data_get($wallets, [User::SLUG_FEE_WALLET, 'balanceFloat'], 0),
'withdraw_wallet_balance' => data_get($wallets, [User::SLUG_WITHDRAW_WALLET, 'balanceFloat'], 0),
],
]);
以下的 Laravel 程式碼代表什麼意思?

定義一個 unique rule, 並且將範圍限定在特定的 user 上, 代表不同 user 之間的訂單不需要 unique

<?php
$orderNumberUniqueRule = Rule::unique($withdrawTable, 'order_number')->where(function ($query) use ($user) {
$query->where('user_id', $user->getKey());
});
以下的 Laravel function 的作用是什麼?
  1. 將 request 裡的參數除了 sign 之外都調出來
  2. 排列這些 key
  3. 首先使用 http_build_query function 針對剛剛的參數來產生一組 url 加密的字串, 然後將這字串與 user 的 secret_key 欄位內的值相串, 然後使用 url 解密這一整個字串, 最後再使用 md5 處理取得 hash 值, 我們比對這個值跟帶進來的 sign 有沒有一樣, 如果不一樣就是不合法
  4. 唯有知道 secret_key 的雙方可以對內容加解密, 而經由這樣的加解密驗證, 確保 request 的內容再傳送過程中未被串改
    <?php
    private function signValid(Request $request, $secretKey)
    {
    $allParametersExceptSign = $request->except('sign');

    ksort($allParametersExceptSign);

    return strcasecmp(
    md5(urldecode(http_build_query($allParametersExceptSign)) . $secretKey),
    $request->sign
    ) === 0;
    }
以下的 Laravel function 的作用是什麼?
  1. 宣告 lock-key 以及持有時間
  2. 嘗試取得 lock-key, 如果不可得, 持續嘗試五秒
  3. 用 transaction 實作, 若有任何錯誤皆返回
  4. 如果無法取得 lock-key, 返回錯誤
  5. 回返錯誤訊息
  6. 如果 lock-key 還被持有中, 釋放 lock-key
    <?php
    public function lock(User $user, $action)
    {
    $lock = Cache::lock($user->lockKey(), 10);

    try
    {
    $lock->block(5);

    return DB::transaction($action);

    } catch (LockTimeoutException $e)
    {
    abort(Response::HTTP_CONFLICT, '请稍候再试');
    } finally
    {
    optional($lock)->release();
    }
    }
以下的 Laravel 程式碼中, balance 是扣款前還是扣款後?

扣款前, 因為 $transaction 還沒被執行完畢

<?php
$transaction = $user->withdrawFloat($request->input('amount'), [
'before_balance' => $user->balance,
]);
以下的 Laravel 程式碼是什麼意思呢?

將資料存入 mysql 中的 json 欄位

<?php
$deposit->user_bank_meta = (object)[
'subbranch' => $userBankCard->subbranch,
'province' => $userBankCard->province,
'city' => $userBankCard->city,
];
下面的 Laravel 程式碼是什麼意思?

將檔案存在 $deposit->getTable(), 檔名為 $deposit->system_order_number, 使用 filesystem.cloud, 可設為 s3

<?php
$request->file('payment_instrument')
->storeAs($deposit->getTable(), $deposit->system_order_number, config('filesystems.cloud'));
下面的 Laravel Requests 代表什麼意思?

使用 captcha 的 extension captcha_api 來驗證, 因為該驗證器一定需要一個 key, 如果在 validation 期間 key 為 null, 那直接就回 500 了, 所以這邊處理, 當沒有 captcha_key 時, 給一個隨機 10 碼, 這樣會驗不過(key 沒帶原本就應該驗不過), 但是不會 500

<?php
public function rules()
{
return [
'username' => 'required_without:email|string',
'email' => 'required_without:username|email',
'password' => 'required|string',
'captcha_key' => 'required',
'captcha' => 'required|captcha_api:'.($this->request->get('captcha_key') ?? Str::random(10)),
];
}
以下的 Laravel 程式碼中, where 內為什麼只有一個參數?

Laravel 中, 如果 request 中的 parameter 與資料庫中的欄位名稱相同, 就可以直接用這種方式 query

<?php
$bankCard = BankCard::where($request->only('card_number'))
->withTrashed()
->first() ?? BankCard::create($data);
以下的 Laravel 程式碼是什麼意思?
1. query 出 card_number 的 model
2. withTrashed 代表強制顯示已被 soft deleted 的 model
3. 取出第一筆
4. 若無結果, 則根據輸入的資料建立一張卡
5. 更新 bankCard
6. 若 bankCard 為 soft deleted, 解除它
<?php
$bankCard = BankCard::where($request->only('card_number'))
->withTrashed()
->first() ?? BankCard::create($data);

$bankCard->update($data);
$bankCard->restore();
在 Laravel 中, 如何從一個 collection 當中取得其中一個 model, 而該 model 中的 price 欄位的值是在這個 collection 的所有 model 之中最小或最大的?
<?php
$min = $data->where('price', $data->min('price'))->first();
// ['name' => 'test', 'price' => 10]
$max = $data->where('price', $data->max('price'))->first();
// ['name' => 'test', 'price' => 600]
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    collection->push($model)
  • Answer:
    將 $model push 到該 collection 中

以下的 Laravel 程式碼代表什麼意思?
1. 帶入欄位的名稱需與資料庫的欄位一樣
2. 依序檢查指定的欄位
3. 若 request 中的該欄位是有值的, 並且該值與目前資料庫中的值是不同的, 這代表有變更產生
4. 將變更的 key 跟 value 放入空的 collection $updateAttributes 中
5. 如果這個 collection 含有 card_number, 這代表卡號變更了, 將 balance => 0 放入 updateAttributes
6. 如果 updateAttributes 是存在的, 開始更新
<?php
$updatedAttributes = collect();

foreach (['card_holder_name', 'card_number', 'bank_name', 'type', 'auto_withdraw'] as $attribute) {
if (!is_null($request->$attribute) && ($request->$attribute != $bankCard->$attribute)) {
$updatedAttributes = $updatedAttributes->merge([$attribute => $request->$attribute]);
}
}

if ($updatedAttributes->has('card_number')) {
$updatedAttributes = $updatedAttributes->merge(['balance' => 0]);
}

if ($updatedAttributes->isNotEmpty()) {
$bankCard->update($updatedAttributes->toArray());
}
Laravel 中, Model 的命名通常是單數還是複數?

單數

Laravel 中的變數命名習慣是?

camel case

Laravel 中, 如何將 namespace, prefix, middleware 同時作用到複數的 route 上?
<?php
Route::group([
'namespace' => 'Worker',
'prefix' => 'worker',
'middleware' => 'check.worker.token',
], function () {
Route::apiResource('working-tasks', 'WorkingTaskController')->only('store', 'update');
Route::apiResource('bank-cards', 'BankCardController')->only('update');

Route::post('captcha-cracks', 'CrackCaptchaController');
});
Laravel 中, 可否在 route 的 group 內再使用一個 group?

可以的

Laravel 中, 巢狀內的 route group 的屬性會不會繼承外層的 group 的屬性?

會的, 像是

<?php
Route::group([
'middleware' => ['auth']
], function () {
Route::get('me', 'AuthController@me');

Route::group([
'namespace' => 'Admin',
'prefix' => 'admin',
'middleware' => ['check.role.admin', 'check.source.admin'],
], function () {
Route::post('users/{user}/reset-password', 'UsersController@resetPassword');
Route::post('users/{user}/reset-withdraw-password', 'UsersController@resetWithdrawPassword');
Route::post('users/{user}/reset-google2fa-secret', 'UsersController@resetGoogle2faSecret');
Route::post('users/{user}/reset-secret-key', 'UsersController@resetSecretKey');
Route::put('users/{user}/delete-group', 'UsersController@deleteGroup')
->where(['user' => '[0-9]+']);
});
});
以下的 Laravel 程式碼中, 邏輯是怎麼樣的?
如果環境是在 production 的話, 檢查來源 ip, 來源 ip 可能有很多個, 取最後一個代表 client, 若不存在則拒絕存取
<?php
public function handle($request, Closure $next)
{
if (app()->environment(['production'])) {
abort_if(!IPs::where('address', Arr::last($request->ips()))->exists(), Response::HTTP_UNAUTHORIZED,
'Invalid source');
}

return $next($request);
}
以下的兩段 Laravel 在 Resource 中的程式碼, 有什麼差異?
  1. <?php
    'withdraws' => $this->whenLoaded('withdraws', Withdraw::collection($this->withdraws))
  2. <?php
    'withdraws' => $this->whenLoaded('withdraws', function () {
    return Withdraw::collection($this->withdraws);
    })

    第一個 block 中, PHP 會先去執行作為參數帶入的 $this->withdraws, 再將結果帶入 whenLoaded, 這便符合了 whenLoaded 的 relation 載入條件, 所以依然會將當前 resource 下的 relation 顯示出來, 實際運行上因為會先執行 $this->withdraws, 因此也會造成效能上的浪費

第二個 block 中, closure 在被呼叫之前, PHP 並不會去解析它, 所以會先執行 whenLoaded 函式, 如果條件吻合, 才會執行 closure, 所以不會去執行 $this->withdraws, 自然 whenLoaded 的條件就不會吻合, resource 中也就不會多撈一層

Laravel 中, 如何將 array 存到資料庫?
  • 資料庫類別為 json

    <?php
    Schema::table('users', function (Blueprint $table) {
    $table->json('iAmarray')->nullable();
    });
  • 存到資料庫前, 先使用 json_encode

    <?php
    data = json_encode($iAmArray);
  • 在 model 加入

    <?php
    protected $casts = [
    'iAmArray' => 'array'
    ];
當我在 Route 當中使用 apiResource 如下, 自動帶入 controller 的 model binding 的變數名稱為?

sub_account

<?php
Route::apiResource('sub-accounts', 'SubAccountsController')->only(['store', 'update']);
Laravel 中, 當我使用 scheduler, 腳本內的 user 務必要使用?

與 webserver 同一個 user

以下的 Laravel 程式碼的邏輯是?
  • 程式碼:

    <?php
    $canSeeSecretKey = optional(auth()->user())->isAdmin() || $this->is(auth()->user());
  • Answer:

    • 如果 auth()->user() 的身份是 admin 的話
    • 如果被帶入 resource 中的 model 的身份跟 auth()->user() 是同一個人的話(代表本人)
以下的 Laravel 程式碼的作用是?
  • code:

    <?php
    public static function depositTypeText()
    {
    return collect((new ReflectionClass(__CLASS__))->getConstants())
    ->filter(
    function ($value, $key) {
    return Str::startsWith($key, 'TYPE_DEPOSIT');
    }
    )
    ->mapWithKeys(
    function ($value, $key) {
    return [$value => $key];
    }
    );
    }
  • Answer:

    • RefectionClass: 取得指定 class 中的資料

    • __class__: 代表當前 class

    • getConstants: 取得 constants

    • filter: 只取符合條件的 key

    • mapWithKeys: 取得符合條件的 key/value pair

      以下的 Laravel 程式碼的邏輯是?
  • Example:

    <?php
    if ($endedAt->diffInDays($startedAt) > 31) {
    $request->merge(
    [
    'ended_at' => (clone $startedAt)->addDays(31)->format('Y-m-d H:i:s'),
    ]
    );
    }
  • Answer:
    如果 $endedAt 跟 $startedAt 相差大於 31 天, 那就把範圍定在最多相差 31 天

2.7 GHz 的 processor, 每秒可以跑多少 cycle?

2,700,000,000

Laravel 中, 何謂 I/O bound code?

Waits for DB queries, HTTP requests, etc…

Laravel 中, 何謂 CPU bound code?

Do a lot of calculation

Laravel 中, 如何增加 php-fpm worker 的數量?

update pm.max_children inside the /etc/php/{version}/fpm/pool.d/www.conf file

Laravel 中, 為何不建議以下的 example 語法?
  • Example:

    <?php
    $posts = POST::whereDate('created_at', '>=', now() )->get();
  • Answer:
    會使用到 MYSQL function, 變成 full table scan

Laravel 中, 何時該使用 chunkById()?

只要 id 是 auto increment primary key, 都使用 chunkById()

Laravel 中, chunkById() 與 chunk() 的差異是?
  • chunk:

    select * from posts offset 0 limit 100
    select * from posts offset 101 limit 100
  • chunkById:

    select * from posts order by id asc limit 100
    select * from posts where id > 100 order by id asc limit 100
Laravel 中, 何時使用 cursor(), 何時使用 chunkById()?

當 DB memory 比較充裕時, 使用 cursor(), 當 APP memory 比較充裕時, 使用 chunkById()

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function getIsYearFeeLateAttribute(): bool
    {
    return $this->memberDetail->{__FUNCTION__}();
    }
  • Answer:
    從 relational model 執行此 function name, 所以在別處也可以用這個寫在 relational model 的 method, 不需要重寫

以下的 Laravel example picture 的意思是?
  • Example:
  • Answer:
    當 client 端傳來的時間是沒有時區的, Laravel 會默認該時間的時區為 Laravel 的 default timezone, 不多做處理直接存到資料庫
以下的 Laravel example picture 的意思是?
  • Example:
  • Answer:
    當 client 端傳來的時間有時區的, Laravel 會將傳來的時間轉換為 default timezone 的時區, 再存到資料庫
以下的 Laravel example picture 的意思是?
  • Example:
  • Answer:
    當 Laravel 從資料庫取得時間後, 會將該時間的時區默認為 Laravel default timezone, 並轉成 ISO8601 格式給 client
以下的 Laravel example picture 的意思是?
  • Example:
  • Answer:
    當 MySQL 的 column type 為 datetime 時, 收到什麼就存什麼, 不另外轉換
以下的 Laravel example picture 的意思是?
  • Example:
  • Answer:
    當 MySQL 的 column type 為 timestamp 時, 會將收到的時間的時區設為當下 MySQL default timezone, 並轉為 UTC timezone 儲存下來, 當使用 select 時, MySQL 會將實際上存為 UTC 的時間轉換為當下 MySQL timezone 的時間
以下的 Laravel example picture 的意思是?
  • Example:
  • Answer:
    當 MySQL 時區為 datetime 時, 不管怎麼調整 MySQL 的時區, 都不會影響回傳的結果
以下的 Laravel example picture 的意思是?
  • Example:
  • Answer:
    當 MySQL 時區為 timestamp 時, 如果去修改 MySQL timezone, 會先將實際儲存的 UTC timezone 時間轉換為該 timezone 的時間, 再回傳
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    $branchManagers = BranchManager::query()
    ->select(['branch_managers.account', 'branch_managers.created_at', 'shops.name', 'branch_managers.status_id'])
    ->join('shops', 'branch_managers.shop_id', '=', 'shops.id')
    ->when($search, function ($query) use ($search) {
    $query->whereIn('branch_managers.id', function ($query) use ($search) {
    $query->select('id')
    ->from(function ($query) use ($search) {
    $query->select('id')
    ->from('branch_managers')
    ->whereRaw('match(account) against (? in boolean mode)', [$search])
    ->union(
    $query->newQuery()
    ->select('branch_managers.id')
    ->from('branch_managers')
    ->join('shops', 'branch_managers.shop_id', '=', 'shops.id')
    ->whereRaw('match(name) against (? in boolean mode)', [$search])
    );
    }, 'matches');
    });
    })->get();
  • Answer:
    無法直接使用 orWhereRaw (match(…) …) 這樣子的 full text search 在多個表格, 因此統一取得 id, 在使用 where in 取得最後需要的資料
    使用 union 來取得兩個 select query 取得的不重複 id
    使用 derived table, 避免 where in 與 union query 之間的 dependency

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    class OrderRequest extends Request
    {
    public function rules()
    {
    return [
    'address_id' => [
    'required',
    Rule::exists('user_addresses', 'id')->where('user_id', $this->user()->id),
    ],
    'items.*.sku_id' => [
    'required',
    function ($attribute, $value, $fail) {
    if (!$sku = ProductSku::find($value)) {
    return $fail('该商品不存在');
    }
    if (!$sku->product->on_sale) {
    return $fail('该商品未上架');
    }
    if ($sku->stock === 0) {
    return $fail('该商品已售完');
    }
    preg_match('/items\.(\d+)\.sku_id/', $attribute, $m);
    $index = $m[1];
    $amount = $this->input('items')[$index]['amount'];
    if ($amount > 0 && $amount > $sku->stock) {
    return $fail('该商品库存不足');
    }
    },
    ],
    'items.*.amount' => ['required', 'integer', 'min:1'],
    ];
    }
    }
  • Answer:
    檢查該 address 是否屬於該 user
    從 items 中使用正則找到該 sku_id, 並檢查庫存是否足夠

以下的 Laravel example code 的意思是?
  • Example:

    <?php

    namespace App\Models;

    class Order extends Model
    {
    use HasFactory;

    protected static function boot()
    {
    parent::boot();
    // 监听模型创建事件,在写入数据库之前触发
    static::creating(function ($model) {
    // 如果模型的 no 字段为空
    if (!$model->no) {
    // 调用 findAvailableNo 生成订单流水号
    $model->no = static::findAvailableNo();
    // 如果生成失败,则终止创建订单
    if (!$model->no) {
    return false;
    }
    }
    });
    }

    public static function findAvailableNo()
    {
    // 订单流水号前缀
    $prefix = date('YmdHis');
    for ($i = 0; $i < 10; $i++) {
    // 随机生成 6 位的数字
    $no = $prefix.str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
    // 判断是否已经存在
    if (!static::query()->where('no', $no)->exists()) {
    return $no;
    }
    }
    \Log::warning('find order no failed');

    return false;
    }
    }
  • Answer:
    在 order model 建立時, 觸發 creating event, 建立訂單流水號

以下的 Laravel example code 的意思是?
  • Example:

    <?php

    namespace App\Http\Requests;

    use App\Models\ProductSku;

    class AddCartRequest extends Request
    {
    public function rules()
    {
    return [
    'sku_id' => [
    'required',
    function ($attribute, $value, $fail) {
    if (!$sku = ProductSku::find($value)) {
    return $fail('该商品不存在');
    }
    if (!$sku->product->on_sale) {
    return $fail('该商品未上架');
    }
    if ($sku->stock === 0) {
    return $fail('该商品已售完');
    }
    if ($this->input('amount') > 0 && $sku->stock < $this->input('amount')) {
    return $fail('该商品库存不足');
    }
    },
    ],
    'amount' => ['required', 'integer', 'min:1'],
    ];
    }
    }
  • Answer:
    使用 closure 驗證商品是否存在, 是否上架, 是否售完, 以及是否有數量但實際庫存不足

以下的 Laravel example code 的意思是?
  • Example:

    <?php

    namespace Database\Factories;

    use App\Models\UserAddress;
    use Illuminate\Database\Eloquent\Factories\Factory;

    class UserAddressFactory extends Factory
    {
    protected $model = UserAddress::class;

    public function definition()
    {
    $addresses = [
    ["北京市", "市辖区", "东城区"],
    ["河北省", "石家庄市", "长安区"],
    ["江苏省", "南京市", "浦口区"],
    ["江苏省", "苏州市", "相城区"],
    ["广东省", "深圳市", "福田区"],
    ];
    $address = $this->faker->randomElement($addresses);

    return [
    'province' => $address[0],
    'city' => $address[1],
    'district' => $address[2],
    'address' => sprintf('第%d街道第%d号', $this->faker->randomNumber(2), $this->faker->randomNumber(3)),
    'zip' => $this->faker->postcode,
    'contact_name' => $this->faker->name,
    'contact_phone' => $this->faker->phoneNumber,
    ];
    }
    }
  • Answer:
    利用 factory 產生住址

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function getSpecifiedConstants($className, $needle)
    {
    return collect((new \ReflectionClass($className))->getConstants())
    ->filter(function ($value, $key) use ($needle) {
    return Str::startsWith($key, $needle);
    });
    }
  • Answer:
    使用 ReflectionClass(), 從指定的 class 中取得定義於該 class 中的 constants, 在使用特定的條件篩選

以下的 Laravel example code, 有何差異?
  • Example:

    <?php
    $rows = [$row1, $row2, $row3]
    DB::table('voucher_statements')->insert($rows);
    Voucher::insert($row1);
  • Answer:
    前者可支援多筆, 但不會 insert created_at 跟 updated_at, 後者只能一次一筆, 但會 insert created_at, updated_at

以下的 Laravel example code 會使用幾筆 SQL query?
  • Example:

    <?php
    $rows = [$row1, $row2, $row3]
    DB::table('voucher_statements')->insert($rows);
  • Answer:
    一筆

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $customers = Customer::inRandomOrder()->take(1)->get();
    $regions = Region::hasCustomer($customers->first())->get();

    return view('customers', [
    'customers' => $customers,
    'regions' => $regions,
    ]);
    }

    public function scopeHasCustomer($query, Customer $customer)
    {
    $query->whereRaw('ST_Contains(regions.geometry, ?)', [$customer->location]);
    }
  • Answer:

    <?php
    public function index()
    {
    // 取得 random user
    $customers = Customer::inRandomOrder()->take(1)->get();
    // 取得該 customer 所在的 region
    $regions = Region::hasCustomer($customers->first())->get();

    return view('customers', [
    'customers' => $customers,
    'regions' => $regions,
    ]);
    }

    public function scopeHasCustomer($query, Customer $customer)
    {
    // 使用 ST_Contains function, arg1 為 regions table 的 geometry column, arg2 為帶入的 customer location, 以取得該 customer 所在的 region
    $query->whereRaw('ST_Contains(regions.geometry, ?)', [$customer->location]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $regions = Region::all();
    $customers = Customer::query()
    ->inRegion(Region::where('name', 'The Prairies')->first())
    ->get();

    return view('customers', [
    'customers' => $customers,
    'regions' => $regions,
    ]);
    }

    public function scopeInRegion($query, Region $region)
    {
    $query->whereRaw('ST_Contains(?, customers.location)', [$region->geometry]);
    }
  • Answer:

    <?php
    public function index()
    {
    $regions = Region::all();
    // 目標在於 select 出, 位於 'The Prairies' 這個 region 的 user
    $customers = Customer::query()
    ->inRegion(Region::where('name', 'The Prairies')->first())
    ->get();

    return view('customers', [
    'customers' => $customers,
    'regions' => $regions,
    ]);
    }

    public function scopeInRegion($query, Region $region)
    {
    // 使用 ST_Contains, arg1 為 geometry format, arg2 為 user location, 所以會 select 出 location 位於此 geometry 的 users
    $query->whereRaw('ST_Contains(?, customers.location)', [$region->geometry]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    protected function seedRegions()
    {
    $this->getRegions()->each(fn ($region) => Region::create([
    'name' => $region['name'],
    'color' => $region['color'],
    'geometry' => (function () use ($region) {
    return DB::raw("ST_SRID(ST_GeomFromText('".$region['geometry']."'), 4326)");
    })(),
    ]));
    }
  • Answer:
    取得 Regions 後, 在 Region table 建立資料, geometry column, 使用 ST_SRID 來設定 spatial reference ID, 4326 表示 world, 使用 ST_GeomFrom Text function, 從 text 建立 geometry data, geometry 格式如下:

    'geometry' => 'MultiPolygon (((-136.21070809681475566 57.03101434136195991, -133.72877891703720366 54.61201637520210284, -133.47202762257742847 53.45604439614419334, -131.01862636440651499 51.6878513109907729, -126.28299137770447658 48.97546429328855311, -125.05629074861900563 48.48622934024960074, -123.37102414358200519 48.11059520996249717, -122.52069777133300477 48.99007009784259736, -113.96448759224449532 48.98897113447335272, -114.59407626641420563 50.4166998690063437, -119.86365984910806048 53.47639943303917676, -119.94099441499190561 59.97238022147455894, -140.92938615814153991 60.06202748779399059, -140.74999819452486349 59.61135341849296054, -138.11897472814712273 58.90840120048653006, -136.21070809681475566 57.03101434136195991)))',
以下的 Laravel example code 中, Multipolygon 的意思是?
  • Example:

    public function getRegions()
    {
    return collect([
    [
    'name' => 'British Columbia',
    'color' => '#F56565',
    'geometry' => 'MultiPolygon (((-136.21070809681475566 57.03101434136195991, -133.72877891703720366 54.61201637520210284, -133.47202762257742847 53.45604439614419334, -131.01862636440651499 51.6878513109907729, -126.28299137770447658 48.97546429328855311, -125.05629074861900563 48.48622934024960074, -123.37102414358200519 48.11059520996249717, -122.52069777133300477 48.99007009784259736, -113.96448759224449532 48.98897113447335272, -114.59407626641420563 50.4166998690063437, -119.86365984910806048 53.47639943303917676, -119.94099441499190561 59.97238022147455894, -140.92938615814153991 60.06202748779399059, -140.74999819452486349 59.61135341849296054, -138.11897472814712273 58.90840120048653006, -136.21070809681475566 57.03101434136195991)))',
    ],
    ]);
    }
  • Answer:
    用多個 point 來代表一個區域

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('regions', function (Blueprint $table) {
    $table->geometry('geometry');
    });
    }
  • Answer:
    geometry column type, 可以用多個 point 連起來代表一個區塊

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    $stores = Store::query()
    ->selectDistanceTo($myLocation)
    ->withinDistanceTo($myLocation, 10000) // 10km
    ->orderByDistanceTo($myLocation)
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }

    public function scopeOrderByDistanceTo($query, array $coordinates, string $direction = 'asc')
    {
    $direction = strtolower($direction) === 'asc' ? 'asc' : 'desc';

    $query->orderByRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) '.$direction, $coordinates);
    }
  • Answer:

    <?php
    public function scopeOrderByDistanceTo($query, array $coordinates, string $direction = 'asc')
    {
    // 由於 $direction 是從外部帶入, 若要直接使用於 orderByRaw, 需要消毒
    $direction = strtolower($direction) === 'asc' ? 'asc' : 'desc';

    // ST_Distance function 取得距離, 第一個 location 為資料庫中的 location column, 第二個為 request user
    // 的 location, 由於 ST_Distance 只接受 valid geographic object, 所以要先轉成 SRID
    // 空一格之後, 再接 $direction
    $query->orderByRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) '.$direction, $coordinates);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('stores', function (Blueprint $table) {
    $table->point('location', 4326);
    });
    }
  • Answer:
    point 為 geographic 的一種形式, 需使用類似 $user->location = \Illuminate\Support\Facades\DB::raw('ST_SRID(Point('.$l.', '.$a.'), 4326)'); 這種方式儲存, $l 為longitude, $a 為 Latitude

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    $stores = Store::query()
    ->selectDistanceTo($myLocation)
    ->withinDistanceTo($myLocation, 10000) // 10km
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }


    public function scopeWithinDistanceTo($query, array $coordinates, int $distance)
    {
    $query->whereRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) <= ?', [...$coordinates, $distance]);
    }
  • Answer:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    $stores = Store::query()
    // select 出 distance column
    ->selectDistanceTo($myLocation)
    // 取得距離在 10km 裡的 record
    // ST_DISTANCE function 得到的結果為 meter
    // 而 10000 meter = 10km
    ->withinDistanceTo($myLocation, 10000) // 10km
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }


    public function scopeWithinDistanceTo($query, array $coordinates, int $distance)
    {
    // 使用 ST_Distance function
    // arg1 為 store 的 location, 可使用 point type column
    // 在儲存時就存成 point 格式, 這樣會增進 select 效能
    // Point(?, ?) 為當前 User 的座標
    // 最後取得 distance <= 10km 的 record
    $query->whereRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) <= ?', [...$coordinates, $distance]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    $stores = Store::query()
    ->selectDistanceTo($myLocation)
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }

    public function scopeSelectDistanceTo($query, array $coordinates)
    {
    if (is_null($query->getQuery()->columns)) {
    $query->select('*');
    }

    $query->selectRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) as distance', $coordinates);
    }
  • Answer:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    // 取得當前 user 的座標與各個 store 之間的距離
    $stores = Store::query()
    ->selectDistanceTo($myLocation)
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }

    public function scopeSelectDistanceTo($query, array $coordinates)
    {
    // 如果當前 $query 沒有取得任何 column 的話, 那就 select 所有 column
    // 因為如果沒這麼做的話, 下面的 query 只會取得 selectRaw 的那一個 column
    if (is_null($query->getQuery()->columns)) {
    $query->select('*');
    }

    // 使用 MySQL 的 ST_Distance 來取得兩個點的距離
    // ST_Distance function 要求的 args 必須要是 valid geographic object
    // 所以使用 ST_SRID function 來取得
    // ST_SRID args 為兩個座標, 以及使用的 spatial reference ID, 4326 代表World
    $query->selectRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) as distance', $coordinates);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    DB::statement('CREATE FULLTEXT INDEX posts_fulltext_index ON posts(title, body) WITH PARSER ngram');
    }
  • Answer:
    加入 fulltext index 到 posts table 的 title, body column, 使用 ngram parser 取代預設 parser

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $posts = Post::query()
    ->with('author')
    ->when(request('search'), function ($query, $search) {
    $query->whereRaw('match(title, body) against(? in boolean mode)', [$search])
    ->selectRaw('*, match(title, body) against(? in boolean mode) as score', [$search]);
    }, function ($query) {
    $query->latest('published_at');
    })
    ->paginate();

    return view('posts', ['posts' => $posts]);
    }
  • Answer:

    <?php
    public function index()
    {
    $posts = Post::query()
    ->with('author')
    // 當 $request->search === true
    ->when(request('search'), function ($query, $search) {
    // 使用 match method, 須事先先加 full-text index, boolean mode 效能較佳
    $query->whereRaw('match(title, body) against(? in boolean mode)', [$search])
    // 增加 score column 代表命中程度
    ->selectRaw('*, match(title, body) against(? in boolean mode) as score', [$search]);
    // 當 $request->search === false, 照 published_at 排序, 因為 match score 排序跟 published_at 排序不同
    }, function ($query) {
    $query->latest('published_at');
    })
    ->paginate();

    return view('posts', ['posts' => $posts]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $devices = Device::query()
    ->orderByRaw('naturalsort(name)')
    ->paginate();

    return view('devices', ['devices' => $devices]);
    }
  • Answer:
    使用 natual sort, 但 MySQL 不支援, 所以必須先在 MySQL 中增加 natualsort function

MySQL 不支援 natural sort, 該怎麼做?

自己增加一個 MySQL function, 如下:

  • Example:
    <?php
    public function up()
    {
    if (config('database.default') === 'mysql') {
    // https://www.drupal.org/project/natsort
    DB::unprepared("
    drop function if exists naturalsort;
    create function naturalsort(s varchar(255)) returns varchar(255)
    no sql
    deterministic
    begin
    declare orig varchar(255) default s;
    declare ret varchar(255) default '';
    if s is null then
    return null;
    elseif not s regexp '[0-9]' then
    set ret = s;
    else
    set s = replace(replace(replace(replace(replace(s, '0', '#'), '1', '#'), '2', '#'), '3', '#'), '4', '#');
    set s = replace(replace(replace(replace(replace(s, '5', '#'), '6', '#'), '7', '#'), '8', '#'), '9', '#');
    set s = replace(s, '.#', '##');
    set s = replace(s, '#,#', '###');
    begin
    declare numpos int;
    declare numlen int;
    declare numstr varchar(255);
    lp1: loop
    set numpos = locate('#', s);
    if numpos = 0 then
    set ret = concat(ret, s);
    leave lp1;
    end if;
    set ret = concat(ret, substring(s, 1, numpos - 1));
    set s = substring(s, numpos);
    set orig = substring(orig, numpos);
    set numlen = char_length(s) - char_length(trim(leading '#' from s));
    set numstr = cast(replace(substring(orig,1,numlen), ',', '') as decimal(13,3));
    set numstr = lpad(numstr, 15, '0');
    set ret = concat(ret, '[', numstr, ']');
    set s = substring(s, numlen+1);
    set orig = substring(orig, numlen+1);
    end loop;
    end;
    end if;
    set ret = replace(replace(replace(replace(replace(replace(replace(ret, ' ', ''), ',', ''), ':', ''), '.', ''), ';', ''), '(', ''), ')', '');
    return ret;
    end;
    ");
    }

    if (config('database.default') === 'sqlite') {
    throw new \Exception('This lesson does not support SQLite.');
    }

    if (config('database.default') === 'pgsql') {
    // http://www.rhodiumtoad.org.uk/junk/naturalsort.sql
    DB::unprepared('
    create or replace function naturalsort(text)
    returns bytea language sql immutable strict as
    $f$ select string_agg(convert_to(coalesce(r[2],length(length(r[1])::text) || length(r[1])::text || r[1]),\'SQL_ASCII\'),\'\x00\')
    from regexp_matches($1, \'0*([0-9]+)|([^0-9]+)\', \'g\') r; $f$;
    ');
    }
    }

    public function down()
    {
    if (config('database.default') === 'mysql') {
    DB::unprepared('drop function if exists naturalsort');
    }

    if (config('database.default') === 'sqlite') {
    throw new \Exception('This lesson does not support SQLite.');
    }

    if (config('database.default') === 'pgsql') {
    DB::unprepared('drop function if exists naturalsort');
    }
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $devices = ['iPhone 3', 'iPhone 11'];
    sort($devices, SORT_NATUAL);
    return $devices;
    }
  • Answer:
    使用 PHP 的 natual sort 參數, 正常 sort 是 iPhone 11, iPhone 3, natual sort 是 iPhone 3, iPhone 11

什麼是 natual sorting?

跟 Alphabetical Sort 幾乎相同, 唯一的不同在於, natual sorting 會將多位數的數字當成一個單一字符
換句話說, ['3', '11', '2'] Alphabetical 排序會是 11, 2, 3, 而 natual sort 會是 2, 3, 11

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function scopeOrderByUpcomingBirthdays()
    {
    $query->orderByRaw('
    case
    when (birth_date + interval (year(?) - year(birth_date)) year) >= ?
    then (birth_date + interval (year(?) - year(birth_date)) year)
    else (birth_date + interval (year(?) - year(birth_date)) + 1 year)
    end
    ', [
    array_fill(0, 4, Carbon::now()->startOfWeek()->toDateString()),
    ]);
    }
  • Answer:

    <?php
    public function scopeOrderByUpcomingBirthdays()
    {
    // 概念為, 將 'birth_date' 加上 '今年到你生日那年總共間隔多少年' 會等於你的 birth_date, 但
    // 年份換成是今年, 如果這個值大於今天的年月日的話, 那代表你的生日還沒過, 反之, 如果這個值小於的話
    // 那代表今年你的生日已經過了, 所以會排到明年去。
    // 這樣一來便可以 order by upcoming birthday
    $query->orderByRaw('
    case
    when (birth_date + interval (year(?) - year(birth_date)) year) >= ?
    then (birth_date + interval (year(?) - year(birth_date)) year)
    else (birth_date + interval (year(?) - year(birth_date)) + 1 year)
    end
    ', [
    // 從 index 0 開始 fill, fill 4 次, fill 這個禮拜的第一天, 以 dateString 的格式
    // 這邊我覺得是看需求, 如果是 weekly email notification 的話, 那就用 startOfWeek()
    // 但若是即時查詢的話, 就要用 now(), 否則會取得生日已經過了的 model
    array_fill(0, 4, Carbon::now()->startOfWeek()->toDateString()),
    ]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->whereBirthdayThisWeek()
    ->orderByBirthday()
    // ->orderByUpcomingBirthdays()
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeOrderByBirthday($query)
    {
    $query->orderByRaw('date_format(birth_date, "%m-%d")');
    }
  • Answer:

    <?php
    public function index()
    {
    $users = User::query()
    ->whereBirthdayThisWeek()
    ->orderByBirthday()
    // ->orderByUpcomingBirthdays()
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeOrderByBirthday($query)
    {
    // order by raw 'birth_date' 欄位, 並以 "%m-%d" 格式排序
    $query->orderByRaw('date_format(birth_date, "%m-%d")');
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    $table->rawIndex("(date_format(birth_date, '%m-%d')), name", 'users_birthday_name_index');
    });
    }
  • Answer:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    // 增加 compound index, 欄位為使用 date_format function
    // reformat 過的 birth_date column, 以及 name column
    $table->rawIndex("(date_format(birth_date, '%m-%d')), name", 'users_birthday_name_index');
    });
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->whereBirthdayThisWeek()
    ->orderByBirthday()
    // ->orderByUpcomingBirthdays()
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeWhereBirthdayThisWeek($query)
    {
    $dates = Carbon::now()->startOfWeek()
    ->daysUntil(Carbon::now()->endOfWeek())
    ->map(fn ($date) => $date->format('m-d'));

    $query->whereRaw('date_format(birth_date, "%m-%d") in (?,?,?,?,?,?,?)', iterator_to_array($dates));
    }
  • Answer:

    <?php
    public function index()
    {
    $users = User::query()
    // 取得生日在本週的 User model
    ->whereBirthdayThisWeek()
    ->orderByBirthday()
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeWhereBirthdayThisWeek($query)
    {
    // 取得 this week 的每一個 date 的日期, 並轉成 'm-d' 格式
    $dates = Carbon::now()->startOfWeek()
    ->daysUntil(Carbon::now()->endOfWeek())
    ->map(fn ($date) => $date->format('m-d'));

    // 取得 birth_date 為這個禮拜的 model, 之所以使用 in 而不是 between, 那是
    // 因為當遇到 12-25 到 1-2 的情況時, 因為前者比後者大, 會出現 query 沒有結果的
    // 問題, 而之所以使用 iterator_to_array, 那是因為 Carbon 會 return 一個
    // generator object
    $query->whereRaw('date_format(birth_date, "%m-%d") in (?,?,?,?,?,?,?)', iterator_to_array($dates));
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('features', function (Blueprint $table) {
    $table->rawIndex("(
    case
    when status = 'Requested' then 1
    when status = 'Approved' then 2
    when status = 'Completed' then 3
    end
    )", 'features_status_ranking_index');
    });
    }
  • Answer:

    <?php
    public function up()
    {
    Schema::create('features', function (Blueprint $table) {
    // 當使用 orderRaw("(
    // case
    // when status = 'Requested' then 1
    // when status = 'Approved' then 2
    // when status = 'Completed' then 3
    // end
    // )")
    // 時, 可加上 index
    $table->rawIndex("(
    case
    when status = 'Requested' then 1
    when status = 'Approved' then 2
    when status = 'Completed' then 3
    end
    // 命名 index
    )", 'features_status_ranking_index');
    });
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    class FeaturesController extends Controller
    {
    public function index()
    {
    $features = Feature::query()
    ->withCount('comments', 'votes')
    ->when(request('sort'), function ($query, $sort) {
    switch ($sort) {
    case 'title': return $query->orderBy('title', request('direction'));
    case 'status': return $query->orderByStatus(request('direction'));
    case 'activity': return $query->orderByActivity(request('direction'));
    }
    })
    ->latest()
    ->paginate();

    return view('features', ['features' => $features]);
    }

    public function scopeOrderByActivity($query, $direction)
    {
    $query->orderBy(
    DB::raw('-(votes_count + (comments_count * 2))'),
    $direction
    );
    }
    }
  • Answer:

    <?php
    class FeaturesController extends Controller
    {
    public function index()
    {
    $features = Feature::query()
    // 取得 comments 以及 votes column counts
    ->withCount('comments', 'votes')
    // 'sort' === 'title' || 'status' || 'activity'
    // request['direction'] 為 desc, 或 asc
    ->when(request('sort'), function ($query, $sort) {
    switch ($sort) {
    case 'title': return $query->orderBy('title', request('direction'));
    case 'status': return $query->orderByStatus(request('direction'));
    case 'activity': return $query->orderByActivity(request('direction'));
    }
    })
    ->latest()
    ->paginate();

    return view('features', ['features' => $features]);
    }
    public function scopeOrderByActivity($query, $direction)
    {
    // 我可以使用 query 中實際或虛擬的 column 來排序
    // - 表示顛倒排序結果
    // 若 votes_count 以及 comments_count 取自虛擬 column, 那是無法做 index 的
    // 可以考慮實際增加兩個欄位, 或是用 virtual column
    $query->orderBy(
    DB::raw('-(votes_count + (comments_count * 2))'),
    $direction
    );
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    class FeaturesController extends Controller
    {
    public function index()
    {
    $features = Feature::query()
    ->withCount('comments', 'votes')
    ->when(request('sort'), function ($query, $sort) {
    switch ($sort) {
    case 'title': return $query->orderBy('title', request('direction'));
    case 'status': return $query->orderByStatus(request('direction'));
    case 'activity': return $query->orderByActivity(request('direction'));
    }
    })
    ->latest()
    ->paginate();

    return view('features', ['features' => $features]);
    }

    public function scopeOrderByStatus($query, $direction)
    {
    $query->orderBy(DB::raw("
    case
    when status = 'Requested' then 1
    when status = 'Approved' then 2
    when status = 'Completed' then 3
    end
    "), $direction);
    }

    }
  • Answer:

    <?php
    class FeaturesController extends Controller
    {
    public function index()
    {
    $features = Feature::query()
    // 取得 comments 以及 votes column counts
    ->withCount('comments', 'votes')
    // 'sort' === 'title' || 'status' || 'activity'
    // request['direction'] 為 desc, 或 asc
    ->when(request('sort'), function ($query, $sort) {
    switch ($sort) {
    case 'title': return $query->orderBy('title', request('direction'));
    case 'status': return $query->orderByStatus(request('direction'));
    case 'activity': return $query->orderByActivity(request('direction'));
    }
    })
    ->latest()
    ->paginate();

    return view('features', ['features' => $features]);
    }
    public function scopeOrderByStatus($query, $direction)
    {
    // 預設 'Requested', 'Approved', 'Completed' 這三個 value 會依照 Alph 方式排序
    // 若想變更排序規則, 可使用以下的方式, 將 value 依照自己想要的順序 return 成數字
    // DB 會依照數字順序排列
    $query->orderBy(DB::raw("
    case
    when status = 'Requested' then 1
    when status = 'Approved' then 2
    when status = 'Completed' then 3
    end
    "), $direction);
    }
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->when(request('sort') === 'town', function ($query) {
    if (config('database.default') === 'mysql' || config('database.default') === 'sqlite') {
    $query->orderByRaw('town is null')
    ->orderBy('town', request('direction'));
    }

    if (config('database.default') === 'pgsql') {
    $query->orderByNullsLast('town', request('direction'));
    }
    })
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }
  • Answer:

    <?php
    public function index()
    {
    $users = User::query()
    ->when(request('sort') === 'town', function ($query) {
    if (config('database.default') === 'mysql' || config('database.default') === 'sqlite') {
    // 如果不加這一行, 那 order by 時會將 null 的顯示在最前面, 加了這一行後, 值為 null 的 row 會被排到最後
    $query->orderByRaw('town is null')
    ->orderBy('town', request('direction'));
    }

    if (config('database.default') === 'pgsql') {
    $query->orderByNullsLast('town', request('direction'));
    }
    })
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function boot()
    {
    Builder::macro('orderByNullsLast', function ($column, $direction = 'asc') {
    $column = $this->getGrammar()->wrap($column);
    $direction = strtolower($direction) === 'asc' ? 'asc' : 'desc';

    return $this->orderByRaw("$column $direction nulls last");
    });
    }
  • Answer:

    <?php
    public function boot()
    {
    // 建立一個 new query builder method, 名為 orderByNullsLast
    Builder::macro('orderByNullsLast', function ($column, $direction = 'asc') {
    // wrap $column, 因為 $column 為外部帶入參數, 並且又使用 raw method, 為避免 SQL injection, 需使用 wrap
    $column = $this->getGrammar()->wrap($column);
    $direction = strtolower($direction) === 'asc' ? 'asc' : 'desc';

    // 此為 PostgreSQL 語法, 效果為將值為 null 的 column 排在最後
    return $this->orderByRaw("$column $direction nulls last");
    });
    }
Laravel 中, updateOrCreate() 與 MySQL 中的 INSERT … ON DUPLICATE KEY UPDATE 的差異是?

基本上兩者的效果相同, 但實作原理不同, 效能也不同
INSERT … ON DUPLICATE KEY UPDATE 為 MySQL 內建語法, 會透過 unique key 去判斷該 row 是否為 duplicate key
updateOrCreate() 為複數 query 實作而成的 method, 會先 select 比較, 再決定 insert or update
前者的效能較佳

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $books = Book::query()
    ->select('book.*')
    ->join('checkouts', 'checkouts.book_id', '=', 'books.id')
    ->groupBy('books.id')
    ->orderByRaw('max(checkouts.borrowed_date) desc')
    ->withLastCheckout()
    ->with('lastCheckout.user')
    ->paginate();

    return view('books', ['books' => $books]);
    }

    public function scopeWithLastCheckout($query)
    {
    $query->addSelect(['last_checkout_id' => Checkout::select('checkouts.id')
    ->whereColumn('book_id', 'books.id')
    ->latest('borrowed_date')
    ->limit(1),
    ])->with('lastCheckout');
    }
  • Answer:

    <?php
    public function index()
    {
    $books = Book::query()
    ->select('books.*')
    ->join('checkouts', 'checkouts.book_id', '=', 'books.id')
    // 如果不使用 group by, 則或出現多個重複的 book model, 因為每一個 book model 都會對應到多個 checkouts model
    ->groupBy('books.id')
    // 取得最大的 borrowed_date, 即最後借出日期, 在 query 過程中, 每個 row 都會新增一個 column 名為 borrowed_date
    // 其值為 max(checkouts.borrowed_date), 最後 order by 這個 column
    // 這邊 INDEX 是不吃的, 若要優化, 可在 book table 中新增一個 last_checkout_id column, 每次 book 被 checkout 時
    // 都更新這個欄位
    ->orderByRaw('max(checkouts.borrowed_date) desc')
    // dynanmic relation 的概念, 每個 book model 對應多個 checkout model, 但我們只取其中一筆 checkout model
    ->withLastCheckout()
    // 已事先於 checkout model 中定義 user belongsTo relation, 所以直接 eager load 對應的 user model
    ->with('lastCheckout.user')
    ->paginate();

    return view('books', ['books' => $books]);
    }

    public function scopeWithLastCheckout($query)
    {
    // 新增 'last_checkout_id' column
    $query->addSelect(['last_checkout_id' => Checkout::select('checkouts.id')
    // 將 checkout table 與 book table 對應起來
    ->whereColumn('book_id', 'books.id')
    // 以 borrowed_date 排序
    ->latest('borrowed_date')
    // 只取第一筆
    ->limit(1),
    // 取得 last_checkout_id 之後, 便可利用 dynanmic relation 的概念, 取得事先定義的 lastCheckout belongsTo relation
    ])->with('lastCheckout');
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $books = Book::query()
    ->orderBy(User::select('name')
    ->join('checkouts', 'checkouts.user_id', '=', 'users.id')
    ->whereColumn('checkouts.book_id', 'books.id')
    ->latest('checkouts.borrowed_date')
    ->take(1)
    )
    ->withLastCheckout()
    ->with('lastCheckout.user')
    ->paginate();

    return view('books', ['books' => $books]);
    }

    public function scopeWithLastCheckout($query)
    {
    $query->addSelect(['last_checkout_id' => Checkout::select('checkouts.id')
    ->whereColumn('book_id', 'books.id')
    ->latest('borrowed_date')
    ->limit(1),
    ])->with('lastCheckout');
    }
  • Answer:

    <?php
    public function index()
    {
    $books = Book::query()
    // 以 name 排序
    ->orderBy(User::select('name')
    // 透過 join, 取得 user 的所有借書紀錄
    ->join('checkouts', 'checkouts.user_id', '=', 'users.id')
    // 透過 where, 取得每一個 book row 對應到的 data, 如果不使用 where 來把 book.id
    // 以及 checkouts.book_id 做關聯, 那 user name 永遠都會是一樣的
    // 但加上 where 之後, 就可以取得每一個 book 的所有借閱紀錄, 並取得最後借出的那個 user name
    // 最後再 order by 這個 user name
    ->whereColumn('checkouts.book_id', 'books.id')
    // 因為要取得最後借出的 user, 所以要先加以排序
    ->latest('checkouts.borrowed_date')
    // 因為要取得最後借出的 user, 因此只取最後的那一個 user
    ->take(1)
    )
    // 利用 dynanmic relation 的概念, 先新增一個 last_checkout_id 虛擬欄位, 再透過
    // book model 中的 belongs to relation, 經由這個虛擬的 last_checkout_id 取得
    // 最後 checkout 的那一筆 checkout 資料
    ->withLastCheckout()
    // checkout model 中已有先定義與 user 的 belongsTo relation, 因此可以取得與
    // 該 checkout model 相關的 user model
    ->with('lastCheckout.user')
    ->paginate();

    return view('books', ['books' => $books]);
    }

    public function scopeWithLastCheckout($query)
    {
    $query->addSelect(['last_checkout_id' => Checkout::select('checkouts.id')
    // 透過 where, 可取得與每一行 book 相關的 checkout records
    ->whereColumn('book_id', 'books.id')
    // 因為每一個 book model 都會有多筆的 checkout records, 因此須加以排序
    ->latest('borrowed_date')
    // 排序後只取得最新的那一筆, 即最後借出那一筆紀錄, 至此, 只為了取得這一筆 records 的 id
    ->limit(1),
    ])->with('lastCheckout');
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->orderByLastLogin()
    ->withLastLogin()
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeOrderByLastLogin($query)
    {
    $query->orderByDesc(Login::select('created_at')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1)
    );
    }

    public function scopeWithLastLogin($query)
    {
    $query->addSelect(['last_login_id' => Login::select('id')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1),
    ])->with('lastLogin');
    }
  • Answer:

    <?php
    public function index()
    {
    $users = User::query()
    ->orderByLastLogin()
    ->withLastLogin()
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeOrderByLastLogin($query)
    {
    // 每個 User 都有很多個 Login, 所以在 orderByDesc 當中使用 subquery
    // 取得與該 user 相關的 login records 之後, 再排序, 然後取第一筆
    // 所以會 orderByDesc 每個 user 的最新一筆 login
    $query->orderByDesc(Login::select('created_at')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1)
    );
    }

    public function scopeWithLastLogin($query)
    {
    // 每個 User 都有 many Login, 利用 addSelect 新增一個 column 'last_login_id'
    // 正常來說 User 跟 Login 的 relationship 應該是 User hasMany Login
    // 但這邊為 dynamic model 的概念, 目的為從 hasMany 眾多 records 當中
    // 只 load 想要的那筆資料, 大幅增進效能
    $query->addSelect(['last_login_id' => Login::select('id')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1),
    ])->with('lastLogin');
    }

    public function lastLogin()
    {
    // 正常來說 User 跟 Login 的 relationship 應該是 User hasMany Login
    // 但這邊為 dynamic model 的概念, 目的為從 hasMany 眾多 records 當中
    // 只 load 想要的那筆資料, 大幅增進效能

    return $this->belongsTo(Login::class);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->select('users.*')
    ->join('companies', 'companies.user_id', '=', 'users.id')
    ->orderBy('companies.name')
    ->with('company')
    ->paginate();

    return view('users', ['users' => $users]);
    }
  • Answer:
    當需要 orderBy hasOne relationship 的某一個 column, 務必使用 join approach

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->select('users.*')
    ->join('companies', 'companies.user_id', '=', 'users.id')
    ->orderBy('companies.name')
    ->with('company')
    ->paginate();

    return view('users', ['users' => $users]);
    }
  • Answer:
    當需要 orderBy belognsTo relationship 的某一個 column, 務必使用 join approach

Laravel 中, 當使用 pagination 時, 務必要使用 orderBy, 為什麼?

因為 pagination 會 offset 並 limit record 數量, 若無使用 orderBy 的話, 很可能每一次的排序都有所不同, 這將導致 pagination 的每一頁的結果是沒有順序性的

Laravel 中, 以下的 migration example code 中, 如果使用 orderBy(‘first_name’)->orderBy(‘last_name’), index 會生效嗎??
  • Example:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    $table->index(['last_name', 'first_name']);
    });
    }
  • Answer:
    不會, 因為 orderBy 順序必須符合 index 的順序

Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    Auth::login(User::where('name', 'Sarah Seller')->first());

    $customers = Customer::query()
    ->visibleTo(Auth::user())
    ->with('salesRep')
    ->orderBy('name')
    ->paginate();

    return view('customers', ['customers' => $customers]);
    }

    public function scopeVisibleTo($query, User $user)
    {
    if (! $user->is_owner) {
    $query->where('sales_rep_id', $user->id);
    }
    }
  • Answer:
    情境為, 當 user 為 owner 時, 可以看到所有的 customer, 而當 user 為 salesRep 時, 只可看到自己底下的 customer
    主要概念為, 若有這樣的情境限制, 務必將 filter 做在 database layer

Laravel 中, 以下的 migration example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    $table->index(['last_name', 'first_name']);
    });
    }
  • Answer:
    增加一個 compound index, 若是使用 orderBy 時, 順序必須跟 compound index 的順序相同

Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = preg_replace('/[^A-Za-z0-9]/', '', $term).'%';
    $query->whereIn('id', function ($query) use ($term) {
    $query->select('id')
    ->from(function ($query) use ($term) {
    $query->select('users.id')
    ->from('users')
    ->where('users.first_name_normalized', 'like', $term)
    ->orWhere('users.last_name_normalized', 'like', $term)
    ->union(
    $query->newQuery()
    ->select('users.id')
    ->from('users')
    ->join('companies', 'users.company_id', '=', 'companies.id')
    ->where('companies.name_normalized', 'like', $term)
    );
    }, 'matches');
    });
    });
    }
  • Answer:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    // 將 $term 中 "非 A-Za-z0-9" 的內容都替換成 '', 也就是刪去
    $term = preg_replace('/[^A-Za-z0-9]/', '', $term).'%';
    // 之所以不在
    $query->whereIn('id', function ($query) use ($term) {
    $query->select('id')
    ->from(function ($query) use ($term) {
    $query->select('users.id')
    ->from('users')
    // 從 first_name_normalized virtual column 中 query
    // 因為若使用 whereRaw 的 regular expression 語法, 將無法
    // 使用 index
    ->where('users.first_name_normalized', 'like', $term)
    ->orWhere('users.last_name_normalized', 'like', $term)
    ->union(
    $query->newQuery()
    ->select('users.id')
    ->from('users')
    ->join('companies', 'users.company_id', '=', 'companies.id')
    ->where('companies.name_normalized', 'like', $term)
    );
    }, 'matches');
    });
    });
    }
Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    $table->foreignId('company_id')->constrained('companies');
    $table->string('first_name_normalized')->virtualAs("regexp_replace(first_name, '[^A-Za-z0-9]', '')")->index();
    $table->string('last_name');
    $table->string('last_name_normalized')->virtualAs("regexp_replace(last_name, '[^A-Za-z0-9]', '')")->index();
    });
    }
  • Answer:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    // foreignId 為 unsignedBigInteger 的 alias, 而 constrained 會自動為
    // users table 中的 company_id 與 companies table 中的 id column 建立 foreign key index
    $table->foreignId('company_id')->constrained('companies');
    // virtualAs 作用為建立一個 virtual column, 此行 code 會將 first_name column 中
    // 只要不是 a-zA-Z0-9 的都替換成 '', 然後再將內容存到 first_name_normalized 這個
    // 虛擬 column, 並建立 index
    $table->string('first_name_normalized')->virtualAs("regexp_replace(first_name, '[^A-Za-z0-9]', '')")->index();
    $table->string('last_name');
    // 同上
    $table->string('last_name_normalized')->virtualAs("regexp_replace(last_name, '[^A-Za-z0-9]', '')")->index();
    });
    }
Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    $query->whereIn('id', function ($query) use ($term) {
    $query->select('id')
    ->from(function ($query) use ($term) {
    $query->select('users.id')
    ->from('users')
    ->where('users.first_name', 'like', $term)
    ->orWhere('users.last_name', 'like', $term)
    ->union(
    $query->newQuery()
    ->select('users.id')
    ->from('users')
    ->join('companies', 'users.company_id', '=', 'companies.id')
    ->where('companies.name', 'like', $term)
    );
    }, 'matches');
    });
    });
    }
  • Answer:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    // 最終目的, 是要找到符合 query 條件的 users, 又要讓 index 生效
    // 這邊是 select * from users whereIn('id', nextQuery)
    $query->whereIn('id', function ($query) use ($term) {
    // 這邊是 select id from derived table, 之所以使用 derived table, 是為了
    // 不要讓內外 query 相互依賴, 如果單純在 whereIn 當中使用 subquery 的話, 就會
    // 產生互相依賴, 造成 index 無法生效
    $query->select('id')
    ->from(function ($query) use ($term) {
    $query->select('users.id')
    ->from('users')
    ->where('users.first_name', 'like', $term)
    ->orWhere('users.last_name', 'like', $term)
    // 使用 union 來串接兩句語法
    ->union(
    $query->newQuery()
    ->select('users.id')
    ->from('users')
    ->join('companies', 'users.company_id', '=', 'companies.id')
    ->where('companies.name', 'like', $term)
    );
    // derived table 必須要有一個 alias
    }, 'matches');
    });
    });
    }
Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    $query->where(function ($query) use ($term) {
    $query->where('first_name', 'like', $term)
    ->orWhere('last_name', 'like', $term)
    ->orWhereIn('company_id', Company::query()
    ->where('name', 'like', $term)
    ->pluck('id')
    );
    });
    });

    }
  • Answer:
    雖然沒有使用 sub query 造成 query 的數量增加了, 但 users table 因為沒有使用 sub query 的關係而使 index 生效, 雖然 query 數量較多, 但速度卻變快了

Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    $query->where(function ($query) use ($term) {
    $query->where('first_name', 'like', $term)
    ->orWhere('last_name', 'like', $term)
    ->orWhereIn('company_id', function ($query) use ($term) {
    $query->select('id')
    ->from('companies')
    ->where('name', 'like', $term);
    });
    });
    });
    }
  • Answer:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    // str_getcsv 會將 ' ' (空白) 分出來, " (double quote) 也分出來, return 一個 array
    // 再把這個 array 使用 each 迭代
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    // prefix 不用 %, 因為 prefix % 不適用於 index
    $term = $term.'%';
    $query->where(function ($query) use ($term) {
    $query->where('first_name', 'like', $term)
    ->orWhere('last_name', 'like', $term)
    // 這邊不使用 orWhereHas, 因為 SQL 語法會關聯兩張表,
    // 這個 dependency 會造成 company_name 不適用 index
    // 因此使用 whereIn 來避開此 dependency
    ->orWhereIn('company_id', function ($query) use ($term) {
    $query->select('id')
    ->from('companies')
    ->where('name', 'like', $term);
    });
    });
    });
    }
Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    $query->where(function ($query) use ($term) {
    $query->where('first_name', 'like', $term)
    ->orWhere('last_name', 'like', $term)
    ->orWhereHas('company', function ($query) use ($term) {
    $query->where('name', 'like', $term);
    });
    });
    });
    }
  • Answer:
    str_getcsv 會把空白隔開的當成一個 value, " 內的也當成一個 value, 再把各個 value 變成 $term 下去 query

Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function show(Feature $feature)
    {
    $feature->load('comments.user');
    $feature->comments->each->setRelation('feature', $feature);

    return view('feature', ['feature' => $feature]);
    }
  • Answer:

    <?php
    public function show(Feature $feature)
    {
    $feature->load('comments.user');

    // 這裡手動的為每個 comments 建立 relation 名為 feature, 代入當前已載入 memory 的 $feature
    // 如此一來, 在上一行 eager load comments 之後, 每一個 comments 都還會有在上一行已經完成
    // eager load 的 feature relation, 那當我們執行 $feature->comments->feature->comments
    // 時就不會重複 SQL 語法, 也不會重複的加載 model
    // 當執行 $feature->comments->feature->comments 時, 如果不使用 eager loading, 會有
    // n+1 issue, 但如果使用 with('comments.feature.comments') 的話, 則會 n*n 的加載不必要
    // 的 model
    // 這樣的 relation 又稱為 circular relation
    $feature->comments->each->setRelation('feature', $feature);

    return view('feature', ['feature' => $feature]);
    }
Laravel 中, 動態 model 的技術概念是?

假設 User model 跟 Login model 的 relation 為 hasMany, 但我在取得 User model 時, 我只想要取得最近 login 的 Login model
所以先用 subquery 來取得一個虛擬的 column 為 login_id, 這時再用這個 login_id 透過 belongsTo 的 relation 取得單筆 model

Laravel 中, 假設今天我想要取得 hasMany relation 中的一筆 record, 這時如果使用 with() 的話會 load 所有的 model, 不使用 with() 的話又會執行很多次 query, 有什麼好的解法?
  • Example:

    <?php
    $users = User::query()
    ->with('login')
    ->orderBy('name')
    ->paginate();
  • Answer:

    <?php
    // 使用 subquery, 從 hasMany relation 中只 query 出我們要的那筆資料, 再將這筆資料變成主 query 中的一個 column
    $users = User::query()
    ->addSelect(['last_login_at' => Login::select('created_at')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1)
    ])
    ->orderBy('name')
    ->paginate();
Laravel 中, 如果使用 composer 安裝套件時, 超過容許的 memory 限制, 可以使用哪個 flag 讓容許 memory 無上限?
COMPOSER_MEMORY_LIMIT=-1
安裝完新的 PHP Extension, 記得要做些什麼事?

重啟 PHP, 重啟 Web server, 若使用 valet, 記得重啟 valet

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    use Illuminate\Validation\Rule;
    use App\Models\Installment;
    .
    .
    .
    public function payByInstallment(Order $order, Request $request)
    {
    // 判断订单是否属于当前用户
    $this->authorize('own', $order);
    // 订单已支付或者已关闭
    if ($order->paid_at || $order->closed) {
    throw new InvalidRequestException('订单状态不正确');
    }
    // 订单不满足最低分期要求
    if ($order->total_amount < config('app.min_installment_amount')) {
    throw new InvalidRequestException('订单金额低于最低分期金额');
    }
    // 校验用户提交的还款月数,数值必须是我们配置好费率的期数
    $this->validate($request, [
    'count' => ['required', Rule::in(array_keys(config('app.installment_fee_rate')))],
    ]);
    // 删除同一笔商品订单发起过其他的状态是未支付的分期付款,避免同一笔商品订单有多个分期付款
    Installment::query()
    ->where('order_id', $order->id)
    ->where('status', Installment::STATUS_PENDING)
    ->delete();
    $count = $request->input('count');
    // 创建一个新的分期付款对象
    $installment = new Installment([
    // 总本金即为商品订单总金额
    'total_amount' => $order->total_amount,
    // 分期期数
    'count' => $count,
    // 从配置文件中读取相应期数的费率
    'fee_rate' => config('app.installment_fee_rate')[$count],
    // 从配置文件中读取当期逾期费率
    'fine_rate' => config('app.installment_fine_rate'),
    ]);
    $installment->user()->associate($request->user());
    $installment->order()->associate($order);
    $installment->save();
    // 第一期的还款截止日期为明天凌晨 0 点
    $dueDate = Carbon::tomorrow();
    // 计算每一期的本金
    $base = big_number($order->total_amount)->divide($count)->getValue();
    // 计算每一期的手续费
    $fee = big_number($base)->multiply($installment->fee_rate)->divide(100)->getValue();
    // 根据用户选择的还款期数,创建对应数量的还款计划
    for ($i = 0; $i < $count; $i++) {
    // 最后一期的本金需要用总本金减去前面几期的本金
    if ($i === $count - 1) {
    $base = big_number($order->total_amount)->subtract(big_number($base)->multiply($count - 1));
    }
    $installment->items()->create([
    'sequence' => $i,
    'base' => $base,
    'fee' => $fee,
    'due_date' => $dueDate,
    ]);
    // 还款截止日期加 30 天
    $dueDate = $dueDate->copy()->addDays(30);
    }

    return $installment;
    }
    .
    .
    .
  • Answer:
    分期付款的 example

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    namespace App\Services;

    use App\Models\Category;

    class CategoryService
    {
    // 这是一个递归方法
    // $parentId 参数代表要获取子类目的父类目 ID,null 代表获取所有根类目
    // $allCategories 参数代表数据库中所有的类目,如果是 null 代表需要从数据库中查询
    public function getCategoryTree($parentId = null, $allCategories = null)
    {
    if (is_null($allCategories)) {
    // 从数据库中一次性取出所有类目
    $allCategories = Category::all();
    }

    return $allCategories
    // 从所有类目中挑选出父类目 ID 为 $parentId 的类目
    ->where('parent_id', $parentId)
    // 遍历这些类目,并用返回值构建一个新的集合
    ->map(function (Category $category) use ($allCategories) {
    $data = ['id' => $category->id, 'name' => $category->name];
    // 如果当前类目不是父类目,则直接返回
    if (!$category->is_directory) {
    return $data;
    }
    // 否则递归调用本方法,将返回值放入 children 字段中
    $data['children'] = $this->getCategoryTree($category->id, $allCategories);

    return $data;
    });
    }
    }
  • Answer:
    使用遞迴, 將資料庫中多層 category 的結構, 撈出並組成 multi array

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    use App\Models\Category;
    .
    .
    .
    public function index(Request $request)
    {
    $builder = Product::query()->where('on_sale', true);
    if ($search = $request->input('search', '')) {
    .
    .
    .
    }

    // 如果有传入 category_id 字段,并且在数据库中有对应的类目
    if ($request->input('category_id') && $category = Category::find($request->input('category_id'))) {
    // 如果这是一个父类目
    if ($category->is_directory) {
    // 则筛选出该父类目下所有子类目的商品
    $builder->whereHas('category', function ($query) use ($category) {
    // 这里的逻辑参考本章第一节
    $query->where('path', 'like', $category->path.$category->id.'-%');
    });
    } else {
    // 如果这不是一个父类目,则直接筛选此类目下的商品
    $builder->where('category_id', $category->id);
    }
    }
    .
    .
    .
    }
    .
    .
    .
  • Answer:
    使用 path string 來取得所有 children rows

以下的 Laravel example code 的意思是?
  • Example:

    <?php

    use App\Models\Category;
    use Illuminate\Database\Seeder;

    class CategoriesSeeder extends Seeder
    {
    public function run()
    {
    $categories = [
    [
    'name' => '手机配件',
    'children' => [
    ['name' => '手机壳'],
    ['name' => '贴膜'],
    ['name' => '存储卡'],
    ['name' => '数据线'],
    ['name' => '充电器'],
    [
    'name' => '耳机',
    'children' => [
    ['name' => '有线耳机'],
    ['name' => '蓝牙耳机'],
    ],
    ],
    ],
    ],
    [
    'name' => '电脑配件',
    'children' => [
    ['name' => '显示器'],
    ['name' => '显卡'],
    ['name' => '内存'],
    ['name' => 'CPU'],
    ['name' => '主板'],
    ['name' => '硬盘'],
    ],
    ],
    [
    'name' => '电脑整机',
    'children' => [
    ['name' => '笔记本'],
    ['name' => '台式机'],
    ['name' => '平板电脑'],
    ['name' => '一体机'],
    ['name' => '服务器'],
    ['name' => '工作站'],
    ],
    ],
    [
    'name' => '手机通讯',
    'children' => [
    ['name' => '智能机'],
    ['name' => '老人机'],
    ['name' => '对讲机'],
    ],
    ],
    ];

    foreach ($categories as $data) {
    $this->createCategory($data);
    }
    }

    protected function createCategory($data, $parent = null)
    {
    // 创建一个新的类目对象
    $category = new Category(['name' => $data['name']]);
    // 如果有 children 字段则代表这是一个父类目
    $category->is_directory = isset($data['children']);
    // 如果有传入 $parent 参数,代表有父类目
    if (!is_null($parent)) {
    $category->parent()->associate($parent);
    }
    // 保存到数据库
    $category->save();
    // 如果有 children 字段并且 children 字段是一个数组
    if (isset($data['children']) && is_array($data['children'])) {
    // 遍历 children 字段
    foreach ($data['children'] as $child) {
    // 递归调用 createCategory 方法,第二个参数即为刚刚创建的类目
    $this->createCategory($child, $category);
    }
    }
    }
    }
  • Answer:
    使用遞迴來儲存 nested 階層

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    namespace App\Models;

    use Illuminate\Database\Eloquent\Model;

    class Category extends Model
    {
    protected $fillable = ['name', 'is_directory', 'level', 'path'];
    protected $casts = [
    'is_directory' => 'boolean',
    ];

    protected static function boot()
    {
    parent::boot();
    // 监听 Category 的创建事件,用于初始化 path 和 level 字段值
    static::creating(function (Category $category) {
    // 如果创建的是一个根类目
    if (is_null($category->parent_id)) {
    // 将层级设为 0
    $category->level = 0;
    // 将 path 设为 -
    $category->path = '-';
    } else {
    // 将层级设为父类目的层级 + 1
    $category->level = $category->parent->level + 1;
    // 将 path 值设为父类目的 path 追加父类目 ID 以及最后跟上一个 - 分隔符
    $category->path = $category->parent->path.$category->parent_id.'-';
    }
    });
    }

    public function parent()
    {
    return $this->belongsTo(Category::class);
    }

    public function children()
    {
    return $this->hasMany(Category::class, 'parent_id');
    }

    public function products()
    {
    return $this->hasMany(Product::class);
    }

    // 定义一个访问器,获取所有祖先类目的 ID 值
    public function getPathIdsAttribute()
    {
    // trim($str, '-') 将字符串两端的 - 符号去除
    // explode() 将字符串以 - 为分隔切割为数组
    // 最后 array_filter 将数组中的空值移除
    return array_filter(explode('-', trim($this->path, '-')));
    }

    // 定义一个访问器,获取所有祖先类目并按层级排序
    public function getAncestorsAttribute()
    {
    return Category::query()
    // 使用上面的访问器获取所有祖先类目 ID
    ->whereIn('id', $this->path_ids)
    // 按层级排序
    ->orderBy('level')
    ->get();
    }

    // 定义一个访问器,获取以 - 为分隔的所有祖先类目名称以及当前类目的名称
    public function getFullNameAttribute()
    {
    return $this->ancestors // 获取所有祖先类目
    ->pluck('name') // 取出所有祖先类目的 name 字段作为一个数组
    ->push($this->name) // 将当前类目的 name 字段值加到数组的末尾
    ->implode(' - '); // 用 - 符号将数组的值组装成一个字符串
    }
    }
  • Answer:
    使用 path 字串來快速取得 parent 以及 children

以下的 Laravel example code 的意思是?
  • Example:

    foreach (
    ['status', 'system_order_number', 'order_number', 'user_card_number', 'system_card_number'] as
    $availableFilterKey
    ) {
    $deposits->when($request->{$availableFilterKey},
    function (Builder $builder, $filterValue) use ($availableFilterKey) {
    $builder->where($availableFilterKey, $filterValue);
    });
    }
  • Answer:
    foreach 依序 loop array 當中的每一個 value
    $availableFilterKey 這裡表示 array 當中的 value
    when 表示條件句, [boolean, executeIfTrue, executeIfFalse]
    $builder 代表 $deposit
    $filterValue 代表 $request->availableFilterKey

在 Laravel 中, 如何取得 query builder?
$query = yourModel::query();
在 Laravel 中, ** 代表什麼意思?
power operator, 如果是 2 ** 16, 代表 2 * 2 * .. 16 次
在 Laravel 中, 如何取得 parent 的 method?
# 如果沒複寫, 直接呼叫即可
$this->method

# 如果有複寫, 也是直接呼叫
$this->method

# 除非你有複寫, 但你要沒複寫的版本
parent::method

參考出處: https://stackoverflow.com/questions/11237511/multiple-ways-of-calling-parent-method-in-php
資料庫欄位 int, 什麼是 signed 跟 unsigned?
signed 的數值範圍橫跨正負數, unsigned 只有正數, 且為 signed 的兩倍, 但最小的負數為 0
可參考文件如下: https://dev.mysql.com/doc/refman/8.0/en/integer-types.html
Laravel 中, 什麼是 coroutine?

像是 thread, 但不會 context switching, 因為 OS 並不知道它的存在, 所以當 Library support non-blocking I/O 時, I/O bound work 可以 run concurrently, CPU bound work 還是 synchronously (因為每一個 process 被分配到的 CPU usage 是相同的)

PHPSTORM 學習筆記 PHP 學習筆記

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×