前一天我們說到了 Repository Pattern 的問題之後,現在回到了 Laravel 專案中,如果不用 Repository Pattern 的話,我們還有什麼選擇呢?
下面範例使用到了自定義的 Query Builder
// Channel.php
use App\Models\Builder\ChannelBuilder;
use Illuminate\Database\Eloquent\Builder;
class Channel extends Base
{
// ...
public static function query(): ChannelBuilder|Builder
{
return parent::query();
}
public function newEloquentBuilder($query): ChannelBuilder
{
return new ChannelBuilder($query);
}
// ...
}
// ChannelBuilder.php
class ChannelBuilder extends Builder
{
use Common;
// ...
public function isPublished(): self
{
return $this->whereStatus(ChannelStatus::published);
}
}
// Common.php
trait Common
{
public function whereStatus(int|BackedEnum $status): self
{
if ($status instanceof BackedEnum) {
$status = $status->value;
}
return $this->where('status', $status);
}
}
// ChannelStatus.php
enum ChannelStatus :int
{
case pending = 0;
case published = 1;
case unpublished = 2;
}
在這個範例中,不僅可以讓查詢情境可以遵守 DRY 原則,而且可以讓你更好去拆解 Model 的常用查詢。在開發過程中,也可以直接讓 IDE 有辦法透過靜態分析找到對應的查詢方法。
Note: 基於上面的作法,所以我不建議使用 laravel 內建的 scope
class LatestEpisodeQuery
{
public function __constructor(
private readonly Channel $channel,
private readonly ?int $limit = null,
): void
{
$this->limit ??= config('episodes.limit');
}
public function get(): Collection
{
return $this
->channel
->episodes()
->isPublished()
->isPremium()
->latest()
->take($this->limit)
->get();
}
}
// usage
(new LatestEpisodeQuery($channel))->get();
在這個範例裡面,還可以將共用的查詢抽出成 query()
,然後去組合你想要的複雜查詢。
一個 Action class 可以包含像是簡單的 CRUD,複雜可以到不同的 model 間的資料處理,你可以想成只有 CRUD 的 repository 加上有一定程度複雜的商業邏輯的組合。
Action 基本上有著高度可以重用的性質與彈性組合的特性,但是實作上其實沒有一個主流的 guideline 可以參考,但是可以嘗試建立類似下面的規則
public method
, 可以叫做是 execute()
或是 run()
,其他在 action 中的方法必須都要是 private method
範例可以參考此文中,所列出來的範例。
雖然在這邊我列入為 方法 3 ,但是實際上我會組合使用方法 1
+ 2
,在大部分甚至稍微複雜的情境都可以輕鬆應對。
在這最後我想要述說的是,上面的方法不僅
是你應該使用或是可以知道的理論,而是可以馬上上手、且實戰的實作方法,如果你有更多的想法請在下面留言,一起討論。