Mekanisme Laravel

7 min read

Published on 20 Oct (updated: 19 Nov)

Written by Irsyad A. Panjaitan

Fill in LaravelTips and Tricks

ArticlesMekanisme Laravel

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:

app/Providers/AppServiceProvider.php
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:

app/Providers/AppServiceProvider.php
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:

app/Providers/AppServiceProvider.php
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.

app/Providers/AppServiceProvider.php
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:

terminal
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:

  1. Model::preventLazyLoading()
  2. Model::preventSilentlyDiscardingAttributes()
  3. 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.

routes/web.php
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:

app/Providers/AppServiceProvider.php
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.

app/Providers/AppServiceProvider.php
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:

app/Providers/AppServiceProvider.php
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.

Irsyad A. Panjaitan

Let's start living like no one can help us in any event, so that when we are helped in certain times, it becomes a plus in itself.
6

Share on