Pengalaman pertama saya menggunakan Laravel Query Scopes mungkin beberapa ribu tahun yang lalu, eh salah hm.... sekitar 5 tahunan lah, karena menemukan permasalahan yang memang mengharuskan saya untuk memanfaatkan Scopes
. Seiring waktu berjalan, saya terus menggunakan Scopes
pada laravel
untuk mempermudah Query
dan membuat kodingan menjadi lebih simple dan mudah difahami.
Wait.... wait..... sepertinya saya harus berhenti bercerita, hm.... saya khawatir kita tidak sehati, pernahkah teman-teman menggunakan Laravel Query Scopes ?
atau sudah taukan teman-teman tentang fitur laravel
yang satu ini ? sepertinya kita harus flashback dulu sebentar..(jeng...jeng... terbayang kayak di filem-filem...).
Kasus.
Pada waktu itu, saya mengerjakan sebuah aplikasi yang usernya
terdiri dari banyak daerah (kabupaten), jadi ceritanya usernya
dari kabupaten gitu :D, setiap user akan menghandle proses dokumen masing-masing daerah (dalam kasus ini satu table untuk menyimpan dokumen dari semua user
), kira-kira seperti inilah struktur tabelnya
id
kabupaten_id
nama_dokumen
...
...
...
Nah, sebelum bisa melakukan upload
dan proses dokumen lainya, user yang dari kabupaten harus login dulu dong tentunya, karena dokumen yang bisa diakses hanya milik user yang sedang login atau administrator jadinya query yang dijalankan akan membutuhkan kondisi (where) yang menunjukan user dari kabupaten mana atau apakah user administrator, kira-kira querynya
kayak begini.
$dokumen = Dokumen::query();
// Jika Administrator
if(auth()->user()->is_admin){
$dokumen = $dokumen->paginate(25);
}elseif(!is_null(auth()->user()->kabupaten_id)){
$dokumen = $dokumen->where(
'kabupaten_id',
auth()->user()->kabupaten_id
)->paginate(25);
}
Dikarenakan data dokumen yang diakses tidak hanya dari satu halaman, jadinya query
diatas akan terus diketik ulang sebanyak yang dibutuhkan, memang melelahkan dan membosankan pastinya, tapi karena memang dibutuhkan ya mau gimana lagi, terpaksa mencari cara yang lebih sederhana.
Cara Tidak Ganteng
hm... walaupun tidak ganteng, cara ini terbukti cukup membantu,karena pada laravel
kita bekerja dengan model
, secara gampangnya kita cukup membuat satu fungsi yang menhandle query
tersebut, dan kemudian fungsi itu kita panggil di semua Controller
yang membutuhkan.
<?php
public static function getFilteredDokumen()
{
$dokumen = Dokumen::query();
// Jika Administrator
if(auth()->user()->is_admin){
$dokumen = $dokumen->paginate(25);
}elseif(!is_null(auth()->user()->kabupaten_id)){
$dokumen = $dokumen->where(
'kabupaten_id',
auth()->user()->kabupaten_id
)->paginate(25);
}
return $dokumen;
}
Masalah terselesaikan, pada controller
kita hanya cukup memanggil Dokumen::getFilteredDokumen()
maka data dokumen pun didapat.
Tetapi sudah benarkah cara diatas ? atau masih ada cara yang lain ? saya jadi teringat (Ceritanya Flashback dalam Flashback), slogan laravel
adalah PHP Framework For Artisan
, jadi secara tidak langsung kode itu harus cantik, tetapi masalahnya cantik tidaknya tergantung dari seorang artisan yang meraciknya.
hm.... berbekal semangat perjuangan, akhirnya mencoba membuka kembali dokumentasi laravel, dengan segenap tenaga yang masih tersisa dan nafas yang sudah hampir pada batasnya, eh maksud saya rokok yang sudah hampir tinggal bungkusnya, saya menemukan sebuah fitur laravel yang bisa membuat peng-queryan menjadi lebih cantik dan sederhana.
Apa itu Laravel Scopes ?
Scopes
adalah sebuah method
yang dapat membuat kita bisa menambahkan database logic
tambahan pada model dengan cara yang lebih terstruktur, dengan tujuan membuat penulisan kode pada model
dan controller
menjadi lebih rapi dan cantik tentunya. kalo dalam dokumentasinya kira-kira gini katanya.
Scopes make it able to reuse query logic by allowing to encapsulate database logic into a model
Oh iya hampir lupa, yang kita bicarakan disini bukan Package Laravel Telescope yang tapi Laravel Query Scopes
Tipe Query Scopes
ada 2, yang pertama yang bisa kita gunakan secara Global (Global Scopse
) dan Scopes
yang hanya bisa digunakan pada satu model
saja (Local Scopes
), pada kasus ini saya menggunakan Global Scopes
karena saya membutuhkan filter semua data (Tidak hanya Dokumen) yang difilter berdasarkan kabupaten_id
dan administrator
. untuk membuat Query Scopes
kita menggunakan perintah
php artisan make:scope FilterScope
perintah tersebut akan membuat sebuah class
baru pada folder app/Scopes/FilterScopes.php
, kira-kita isinya seperti berikut:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class FilterScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
}
}
karena saya ingin memfilter semua data berdasarkan kabupaten_id
dan administrator
, saya menambahkan beberapa baris kode, sehingga class FilterScopes
menjadi seperti berikut.
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class FilterScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
if(auth()->user()->is_admin){
// karena tidak ada flag untuk admin
// asumsi saya semua kabupaten_id
//yang tidak null adalah milik administrator.
$builder->whereNotNull('kabupaten_id');
}elseif(!is_null(auth()->user()->kabupaten_id)){
$kab_id = auth()->user()->kabupaten_id;
$builder->where('kabupaten_id', $kab_id);
}
}
}
sejauh ini Scopes
sudah selesai, kita hanya perlu mengimplementasikanya kedalam model
yang membutuhkan filter tersebut, dilaravel model
ada sebuah method
yang akan terus dijalankan tanpa kita harus memanggilnya, secara default method
ini tidak akan terlihat pada model kita, karena merupakan turunan dari class Model
milik laravel, karena jenisnya adalah warisan dari class Model
, method
ini bisa kita overide
alias kita timpa dengan catatan memiliki nama yang sama, untuk itu kita perlu membuat satu method
baru dalam model
milik kita dengan nama boot
<?php
namespace App\Models;
use App\Scopes\FilterScope;
use Illuminate\Database\Eloquent\Model;
class Dokumen extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new FilterScope());
}
}
Dengan demikian setiap kita memanggil Model Dokumen
secara otomatis filter tersebut akan diterapkan, sehingga pemanggilan pada controller atau ditempat lainya akan menjadi lebih sederhana, misalnya seperti.
$dokumen = Dokumen::paginate(25);
kita tidak perlu lagi menuliskan query where
yang panjang, jika nantinya dibutukan perubahan filter kita cukup memodifikasi FilterScope.php
, dengan demikian kode pada model
dan controller
menjadi lebih rapi dan mudah untuk dibaca.
eh bang..... tunggu deh, bukanya bisa pake whitoutGlobalScope
ya kalo misal datanya mau dipanggil tanpa filter ? tentu bisa dong, misal ni ketika yang login bukan administrator
atau user
yang tidak memiliki kabupaten_id
tetapi membutuhkan data dokumen, kita cukup memanggil model dengan menambahkan method whitoutGlobalScope
, misalnya seperti.
Dokumen::withoutGlobalScope(FilterScope::class)->paginate(25);
atau bisa juga menggunakan anonymous global scope
, fungsi ini sedikit berbeda, karena kita bisa mendifinisikan filter mana saja yang akan diabaikan ketika model tersebut dipanggil, misalnya seperti
Dokumen::withoutGlobalScope('dokumen_id')->paginate(25)
artinya jika filter kita memiliki beberapa kondisi, maka kondisi pada where('dokumen_id')
akan diabaikan.
Terus Local Scope
gimana bang ? sabar sob, bakar rokok dulu (ceritanya udah ga flashback nih). nah misalnya biasanya kita melakukan filter where
untuk kabupaten Benermeriah
, kebetulan id-nya 10
, kan cukup singkat tu misalnya
Dokumen::where('dokumen_id', 10)->get();
tentunya dengan cara tersebut masalah selesai. data yang kita harapkan sesuai, tetapi sebenarnya ada lagi cara yang lebih cantik dan elegan, karena bisa menghandle beberapa kondisi sekaligus, misalnya kasusnya, kita pengen mencari dokumen yang kabupaten_id
nya 10
dan jenisnya pdf
, kita bisa saja menggunakan cara biasa
Dokumen::where('kabupaten_id', 10)
->where('jenis','pdf');
dengan begitu masalah juga selesai, tetapi karena kita sedang bercerita naglor ngidul tentang Query Scope
maka sebaiknya cara-cara seperti itu kita Scope
-kan, sehingga menjadi
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Dokumen extends Model
{
public function scopeDokumenPdf($query, $kab_id, $jenis)
{
return $query->where('kabupaten_id', $kab_id)
->where('jenis', $jenis);
}
}
sehingga pemanggilan pada controller
akan menjadi lebih sederhana, kita cukup memanggil nama method
yang barusan kita buat tanpa mengikutsertakan embel-embel scope
.
$dokumen = Dokumen::DokumenPdf(10,'pdf')->paginate(25);
nah itulah yang disebut dengan Local Scope
, Local Scope
hanya berada dan bisa digunakan oleh satu model
saja, namanya juga Local
, sama seperti Panda Local
, Pokemon Local
dll, ehem.... teman-teman pasti fahamlah ya.