Anladığım kadarıyla Repository Pattern'de bütün olay "istenirse Eloquent'ten sıyrılmak" ve "IoC avantajlarından yararlanmak" arasında dönüyor.
Ben ileride cache
kullanabilmek adına "istenirse Eloquent'ten sıyrılmak" kısmına yoğunlaşmak istiyorum.
Örneği basit tutabilmek için basit bir Article
modeli üzerinden devam edeceğim.
class Article extends Model
{
protected $table = 'articles';
protected $dates = [
'created_at',
'updated_at'
];
protected $fillable = [
'title',
'content',
'subject',
'publisher',
'likes',
'created_at'
];
// ...
}
Diyelim ki ben bu model ile ilgili okuma işlemlerini ister Eloquent
ister cache
üzerinden yapmak isteyeceğim. Model için bariz metotlar içeren bir Repository oluşturuyorum:
interface ArticleQueries
{
public function getById(int $articleId): Article;
public function getLastArticles(int $limit);
public function getTopArticles(int $limit);
}
Bu arayüzü Eloquent'e uyarlıyorum:
final class EloquentArticleQueries implements ArticleQueries
{
/**
* @param int $articleId
* @return Article
*/
public function getById(int $articleId): Article
{
return Article::findOrFail($articleId);
}
/**
* @param int $limit
* @return Article[] | Collection
*/
public function getLastArticles(int $limit = 10)
{
return Article::orderBy('created_at', 'desc')
->limit($limit)
->get();
}
/**
* @return Article[] | Collection
*/
public function getTopArticles(int $limit = 10)
{
return Article::orderBy('likes', 'desc')
->limit($limit)
->get();
}
}
Servis sağlayıcısında bu ikisini birbirine bağlıyorum:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(
ArticleQueries::class,
EloquentArticleQueries::class
);
// ...
}
}
Artık uygulamamda bu arayüzü kullanabilirim. Şimdi kafamı kurcalayan bölüme geçelim...
Bütün makale başlıklarını bir tabloda göstermek istiyorum ve çok fazla makale olduğu için bu sonuçları da sayfalamam gerekiyor. Bunun için paginate()
metodunu kullanacağım:
final class EloquentArticleQueries implements ArticleQueries
{
// ...
/**
* @return LengthAwarePaginator
*/
public function getPaginatedArticles(int $limit = 10): LengthAwarePaginator
{
return Article::paginate($limit);
}
}
Şimdi de tablodaki makaleleri yayıncıya (publisher
) göre filtreleyebilmek istiyorum. Formu oluşturdum bile:
<form method="POST" action="{{ route('articles') }}">
<input type="text" name="filter_publisher">
<button type="submit">Yayıncıya Göre Filtrele</button>
</form>
getPaginatedAndFilteredArticles()
gibi yeni bir metot mu oluşturmalıyım yoksa elimdeki metoda yeni parametre mi eklemeliyim? Parametre ekleyerek devam edeceğim.
final class EloquentArticleQueries implements ArticleQueries
{
// ...
/**
* @return LengthAwarePaginator
*/
public function getPaginatedArticles(int $limit = 10, ?string $filter = null): LengthAwarePaginator
{
return Article::when(
$filter, function (Builder $q) use ($filter) {
$q->where('publisher', 'LIKE', '%' . $filter . '%');
})
->paginate($limit);
}
}
Filtreleme özelliğini makale konusu (subject
) sütununa da uyarlamak istedim:
<form method="POST" action="{{ route('articles') }}">
<input type="text" name="filter_subject">
<button type="submit">Konuya Göre Filtrele</button>
</form>
Elimdeki metoda yeni bir parametre daha ekliyorum:
final class EloquentArticleQueries implements ArticleQueries
{
// ...
/**
* @return LengthAwarePaginator
*/
public function getPaginatedArticles(int $limit = 10, ?string $filter = null, ?string $filterColumn = null): LengthAwarePaginator
{
return Article::when(
$filter, function (Builder $q) use ($filter, $filterColumn) {
$q->where($filterColumn, 'LIKE', '%' . $filter . '%');
})
->paginate($limit);
}
}
Şimdi de makaleleri oluşturulma tarihine (created_at
) göre azalan ya da artan olarak sıralamak istiyorum:
<form method="POST" action="{{ route('articles') }}">
<select name="order_created_at">
<option value="desc">Azalan</option>
<option value="asc">Artan</option>
</select>
<button type="submit">Oluşturulma Tarihine Göre Sırala</button>
</form>
final class EloquentArticleQueries implements ArticleQueries
{
// ...
/**
* @return LengthAwarePaginator
*/
public function getPaginatedArticles(int $limit = 10, ?string $filter = null, ?string $filterColumn = null, string $orderDirection = 'desc'): LengthAwarePaginator
{
return Article::orderBy('created_at', $orderDirection)
->when(
$filter, function (Builder $q) use ($filter, $filterColumn) {
$q->where($filterColumn, 'LIKE', '%' . $filter . '%');
})
->paginate($limit);
}
}
Makaleleri beğeni sayısına (likes
) göre de sıralamak istersem?:
<form method="POST" action="{{ route('articles') }}">
<select name="order_likes">
<option value="desc">Azalan</option>
<option value="asc">Artan</option>
</select>
<button type="submit">Beğeni Sayısına Göre Sırala</button>
</form>
final class EloquentArticleQueries implements ArticleQueries
{
// ...
/**
* @return LengthAwarePaginator
*/
public function getPaginatedArticles(int $limit = 10, ?string $filter = null, ?string $filterColumn = null, string $orderDirection = 'desc', string $orderColumn = 'created_at'): LengthAwarePaginator
{
return Article::orderBy($orderColumn, $orderDirection)
->when(
$filter, function (Builder $q) use ($filter, $filterColumn) {
$q->where($filterColumn, 'LIKE', '%' . $filter . '%');
})
->paginate($limit);
}
}
Sadece bu ay yazılmış makaleleri göstermek istersem nasıl olacak?
<form method="POST" action="{{ route('articles') }}">
<input type="checkbox" name="only_current_month"> Sadece bu ay yazılanlar
<button type="submit">Yazılma Tarihine Göre Filtrele</button>
</form>
final class EloquentArticleQueries implements ArticleQueries
{
// ...
/**
* @return LengthAwarePaginator
*/
public function getPaginatedArticles(int $limit = 10, ?string $filter = null, ?string $filterColumn = null, string $orderDirection = 'desc', string $orderColumn = 'created_at', bool $currentMonth = false): LengthAwarePaginator
{
return Article::where('created_at', )
->orderBy($orderColumn, $orderDirection)
->when(
$filter, function (Builder $q) use ($filter, $filterColumn) {
$q->where($filterColumn, 'LIKE', '%' . $filter . '%');
})
->when(
$currentMonth, function (Builder $q) {
$q->whereMonth('created_at', now()->month);
})
->paginate($limit);
}
}
Ya da bu yıl yazılmış olanlar? Son güncellenen makaleler? ...
Gördüğünüz gibi metot parametreleri saçma bir şekilde uzayıp gitmeye başladı. Hani god method diye bir tabir var mıdır bilmiyorum ama olsaydı böyle bir şeye benzerdi sanırım. Her bir zincir halkası için yukarıda yaptığım gibi yeni bir parametre mi atamam gerekiyor yoksa her işlem için başka bir metot mu tanımlamam gerekiyor? Doğru mu yapıyorum?
Şu duruma da dikkatinizi çekmek istiyorum: Örneği sadece tek model üzerinden verdim. Bir başka modelde sayfalamaya ya da sıralamaya ihtiyaç duyulmayabilir (getAll()
?), bir başkasında sıralama olabilir fakat sayfalama olmayabilir (getSorted()
? getAll(string $sortBy, string $direction)
?) gibi gibi... Veritabanından bir şeyler çekebilmek için her bir modele özel farklı isimlerde metotlar tanımlama fikri hoşuma gitmedi açıkçası.
Bir kaydın var olup olmadığını anlamak için de başka bir metot mu tanımlamak gerek?
checkIfExistsByUserId(int $userId, int $articleId): bool
{
return Article::where('user_id', $userId)
->where('article_id', $articleId)
->count() > 0;
}
checkIfExistsByPublisher(string $publisher, int $articleId): bool
{
return Article::where('publisher', $publisher)
->where('article_id', $articleId)
->count() > 0;
}
// ...
Konuyla ilgili her türlü görüşe açığım. Yani şu olayı anlayabilmek için o kadar uğraştım ki her şeyden vazgeçip bütün veritabanı işlemlerini Route
callback'leri içinde yapmama ramak kaldı.