Memanfaatkan Query Scope Pada Laravel

Khairu Aqsara Sudirman

Khairu Aqsara Sudirman

Oct 04, 2020 — 6 mins read
Photo by <a href="https://unsplash.com/@picsbyjameslee" target="_blank">James Lee</a> on <a href="https://unsplash.com" target="_blank">Unsplash</a>

Photo by James Lee on Unsplash

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. 

laravel php
Read More

__get() dan __set() Pada Laravel Model

Magic method secara default ada pada semua class yang didifinisikan oleh PHP, hanya saja sebagian tidak terlihat, yang paling umum adalah __construct() atau __desctruct(), saya tidak akan menjelaskan semua tentang magic method pada tulisan ini, karena saya ingin lebih fokus kepada laravel model.

Read More

Membuat Backup Database Otomatis pada Laravel

Pada dasarnya, proses backup merupakan satu workflow yang harus dilakukan dalam pemeliharaan sebuah aplikasi, dengan tujuan untuk menghindari kehilangan data, sejatinya proses ini bisa dilakukan secara manual, semi otomatis maupun full otomatis, tergantung dari sudut mana kita melihat kemudahanya.