Mekanisme Laravel
Dalam artikel ini saya akan memberikan beberapa rule yang direkomendasikan dalam menggunakan Laravel.
Share
Dalam artikel ini saya akan memberikan beberapa rule yang direkomendasikan dalam menggunakan Laravel.
N+1 Issue
Anggap dulu Anda membuat aplikasi toko online yang pastinya akan menggunakan tabel yang namanya produk yang biasanya berelasi ke kategori. Apa yang kita lakukan adalah menampilkan produk beserta dengan kategori yang kurang lebih struktur untuk tabel produk akan seperti ini:
products id - bigint category_id - bigint title - string categories id - bigint name - string slug - string
bashCopy
Perhatikan tanda yang saya beri untuk kolom category_id
, yang itu artinya setiap produk yang kita buat pastinya mempunyai id dari tabel kategori. Saya akan mencoba untuk menampilkan produknya yang itu kurang lebih seperti ini:
$products = Product::get(); foreach($products as $product) { echo $product->title . ' / ' . $product->category->name . '<br/>'; }
phpCopy
Apa yang dilakukan dari kode di atas adalah membuat query yang berulang pada tabel kategori. Lihat query di bawah:
select * from `products` select * from `categories` where `categories`.`id` = 1 limit 1 select * from `categories` where `categories`.`id` = 1 limit 1 select * from `categories` where `categories`.`id` = 1 limit 1 select * from `categories` where `categories`.`id` = 1 limit 1 select * from `categories` where `categories`.`id` = 1 limit 1 select * from `categories` where `categories`.`id` = 2 limit 1 select * from `categories` where `categories`.`id` = 2 limit 1 ...
sqlCopy
Jika Anda ingin melihat query, coba pakai laravel debugbar, maka Anda akan melihat query yang berulang behind the scene nya. Anda mungkin sudah tahu, untuk menangani hal seperti ini bisa dengan yang namanya eager loading.
//$products = Product::get(); $products = Product::with('category:id,name,slug')->get(); foreach($products as $product) { echo $product->title . ' / ' . $product->category->name . '<br/>'; }
phpCopy
Tetapi poin nya bukan itu, saya ingin memberi tahu, bahwa Anda bisa melakukan yang namanya prevention dengan menambahkan 1 bari kode pada AppServiceProvider
. By default, dia akan seperti ini kurang lebih:
1namespace App\Providers; 2 3use Illuminate\Support\ServiceProvider; 4 5class AppServiceProvider extends ServiceProvider 6{ 7 public function register() 8 { 9 // 10 } 11 12 public function boot() 13 { 14 // 15 } 16} 17
phpCopy
Dan kita akan menambahkan yang tepat pada method boot
seperti:
1namespace App\Providers; 2 3use Illuminate\Database\Eloquent\Model; 4use Illuminate\Support\ServiceProvider; 5 6class AppServiceProvider extends ServiceProvider 7{ 8 public function register() {...} 9 10 public function boot() 11 { 12 Model::preventLazyLoading(!$this->app->isProduction()); 13 } 14} 15
phpCopy
Perhatikan bahwa disana saya memberikan !$this->app->isProduction()
pada parameter nya, itu artinya error akan muncul pada saat mode local
saja, jika sudah mode production
, maka itu akan tidak kelihatan lagi error nya. Anda bisa set environment nya pada file .env
.
Dan sekarang, jika Anda hilangkan eager load nya pada saat menampilkan produk, maka harusnya akan ada error exception yang kita terima. Perhatikan kode dibawah ini:
$products = Product::get(); //$products = Product::with('category:id,name,slug')->get(); foreach($products as $product) { echo $product->title . ' / ' . $product->category->name . '<br/>'; }
phpCopy
Lihat di browser, maka akan ada error exception yang itu dari LazyLoadingViolationException
.
Attempted to lazy load [category] on model [App\Models\Product] but lazy loading is disabled.
bashCopy
Namun, jika kita ingin menampilkan error secara diam-diam, bisa dibilang kita ingin melihat ni apa-apa saja yang kena lazy load saat production. Kita bisa menggunakan yang namanya handleLazyLoadingViolationUsing
, untuk itu kita bisa buat kode nya seperti:
public function boot() { Model::preventLazyLoading(); if ($this->app->isProduction()) { Model::handleLazyLoadingViolationUsing(function ($model, $relation) { $class = get_class($model); info("Attempted to lazy load [{$relation}] on model [{$class}]."); }); } }
phpCopy
Maka dengan seperti itu, jika aplikasi kita sudah di mode production dan memang ada yang memiliki isu n+1. Harusnya error akan muncul pada storage/logs/laravel.log
. Jika Anda ingin melihat bagaimana melihat log ini dengan mudah, Anda bisa lihat pada artikel ini: Penampil Log yang Mantap untuk Laravel
Polymorphic mapping
Relasi yang kita gunakan jika kita ingin memanfaatkan satu tabel yang itu bisa di pakai di beberapa tabel. Bayangkan jika Anda mempunyai tabel artikel dan video dengan masing-masing dari mereka mempunyai komentar. Dan itu harusnya bisa dibuat 1 tabel saja untuk komentarnya, dan akan dipakaikan ke tabel artikel dan video.
Jadi kurang lebih struktur tabel nya akan seperti:
articles id - bigint title - string videos id - bigint title - string comments id - bigint user_id - bigint body - text commentable_id - bigint commentable_type - string
bashCopy
Dan harusnya isi dari kolom commentable_type
akan menunjukkan model yang dipakai seperti:
mysql> select id, body, commentable_type, commentable_id from comments; +----+------+--------------------+----------------+ | id | body | commentable_type | commentable_id | +----+------+--------------------+----------------+ | 1 | ... | App\Models\Article | 3 | | 2 | ... | App\Models\Article | 44 | | 3 | ... | App\Models\Video | 1 | | 4 | ... | App\Models\Article | 4 | | 5 | ... | App\Models\Video | 66 | | 6 | ... | App\Models\Video | 64 | +----+------+--------------------+----------------+
bashCopy
Ini adalah standard dari laravel yang memasukkan full namespace dari model tersebut. Yang itu bisa dibilang tidak good practice. Kenapa seperti itu, bayangkan jika kita merubah classname nya dengan alasan yang bisa saja. Maka semua kode akan break karena telah mengambil class tersebut sebagai identifier nya.
Untuk merubah default ini kita bisa menggunakan yang namanya morphMap
pada metode boot
yang ada pada AppServiceProvider
.
1namespace App\Providers; 2 3use Illuminate\Database\Eloquent\Model; 4use Illuminate\Database\Eloquent\Relations\Relation; 5use Illuminate\Support\ServiceProvider; 6 7class AppServiceProvider extends ServiceProvider 8{ 9 public function register() {...} 10 11 public function boot() 12 { 13 ... 14 Relation::morphMap([ 15 'video' => \App\Models\Video::class, 16 'article' => \App\Models\Article::class, 17 ]); 18 } 19} 20
phpCopy
Dengan begitu, maka nanti setiap ada penambahan file harusnya akan sudah di map dengan ketentuan yang sudah kita buat. Namun, jika data yang Anda miliki sudah ada, jangan lupa untuk memperbarui nya secara manual ya seperti:
UPDATE `comments` SET commentable_type = 'video' WHERE commentable_type = 'App\\Models\\Video'
sqlCopy
Dengan begitu, harusnya sekarang akan sudah berubah, dan jangan lupa update juga untuk artikel nya. Jadi sekarang jika kita lihat isi tabel nya akan sudah berubah seperti:
mysql> select id, body, commentable_type, commentable_id from comments; +----+------+------------------+----------------+ | id | body | commentable_type | commentable_id | +----+------+------------------+----------------+ | 1 | ... | article | 3 | | 2 | ... | article | 44 | | 3 | ... | video | 1 | | 4 | ... | article | 4 | | 5 | ... | video | 66 | | 6 | ... | article | 64 | +----+------+------------------+----------------+
bashCopy
Model strictness
Sejak laravel versi 9.35.0
, kita telah diberikan metode baru untuk model yaitu Model::shouldBeStrict
, dimana metode 3 strictness eloquent yaitu:
- Model::preventLazyLoading()
- Model::preventSilentlyDiscardingAttributes()
- Model::preventsAccessingMissingAttributes()
Sebelum kita lanjut lebih jauh, disini saya akan menunjukkan struktur dari tabel users yang saya miliki.
users id - bigint name - string email - string email_verified_at - datetime password - string remember_token - string created_at - datetime updated_at - datetime
bashCopy
Baik, sekarang saya akan mencoba menampilkan single user dengan mencoba memasukkan field yang tidak ada di dalam struktur tabel nya yaitu about
.
Route::get('users/{user}', function (User $user) { return [ "id" => $user->id, "name" => $user->name, "about" => $user->about, "created_at" => $user->created_at->format('j M Y, g:i a'), ]; });
phpCopy
Seharusnya, by default about
akan menjadi null
, lihat outputnya di browser akan sudah seperti:
{ "id": 3, "name": "Jimmy Page", "about": null, "created_at": "20 Oct 2022, 4:41 am" }
jsonCopy
Dan jika Anda perhatikan baik-baik, json itu menampilkan semua kolom yang ada di tabel users kecuali password, dan sudah pasti. Disini kita tidak punya yang namanya kolom about
. Sekarang, saya akan menambahkan 1 baris kode lagi pada metode boot seperti:
public function boot() { Model::shouldBeStrict(); Model::preventLazyLoading(); if ($this->app->isProduction()) {...} Relation::morphMap([...]); }
phpCopy
Perhatikan pada baris yang tandai, itu adalah penambahan yang saya maksud, guna untuk memberitahu kita, mana nih attribute yang tidak ada dalam kolom tabel nya yang kita coba untuk tampilkan. Refresh di browser, dan sekarang akan muncul exception error dari MissingAttributeException
yang kurang lebih itu seperti:
The attribute [about] either does not exist or was not retrieved for model [App\Models\User].
bashCopy
Sama layaknya seperti preventLazyLoading
, kita tentunya juga bisa membuat ini diam-diam di waktu mode production.
public function boot() { Model::shouldBeStrict(); Model::preventLazyLoading(); if ($this->app->isProduction()) { Model::handleLazyLoadingViolationUsing(function ($model, $relation) { $class = get_class($model); info("Attempted to lazy load [{$relation}] on model [{$class}]."); }); Model::handleLazyLoadingViolationUsing(...); } Relation::morphMap(...); }
phpCopy
Dan jika Anda memang tidak ingin menampilkan exception lewat log laravel, entah mungkin itu dengan alasan tersendiri. Anda bisa matikan itu dengan cara seperti:
public function boot() { Model::preventAccessingMissingAttributes(); Model::preventSilentlyDiscardingAttributes(); Model::preventLazyLoading(!$this->app->isProduction()); Relation::morphMap([ 'video' => \App\Models\Video::class, 'article' => \App\Models\Article::class, ]); }
phpCopy
Saya sengaja mengaktifkan preventAccessingMissingAttributes
, karena itu bergantung dengan kebenaran data yang kita tidak tampilkan, kita tidak bisa menjamin sampai mana typo yang kita lakukan. Jadi harusnya, dengan adanya metode ini, memudahkan kita melihat dimana kesalahan kita untuk kemudian kita benarkan.
Kesimpulan
Bisa sama-sama kita simpulkan bahwa menggunakan laravel itu yang penting explicit. Semua yang diberikan by default itu bisa dibilang implicit, sehingga tugas kita sebagai developer membuatnya explicit. Semoga artikel ini bermanfaat, saya Irsyad. Dan saya akan melihat Anda nanti di artikel selanjutnya.