diff --git a/LARAVEL_BEST_PRACTICE.md b/LARAVEL_BEST_PRACTICE.md new file mode 100644 index 0000000..03fd37e --- /dev/null +++ b/LARAVEL_BEST_PRACTICE.md @@ -0,0 +1,591 @@ +You might also want to check out the [real-world Laravel example application](https://github.com/alexeymezenin/laravel-realworld-example-app) + +## Konten + +[Prinsip _single responsibility_](#prinsip-single-responsibility) + +[Model tebal, _controller_ tipis](#model-tebal-controller-tipis) + +[Validasi](#validasi) + +[_Business logic_ harus di dalam kelas _services_](#business-logic-harus-di-dalam-kelas-services) + +[_Don't repeat yourself_ (DRY)](#dont-repeat-yourself-dry) + +[Lebih memilih menggunakan _Eloquent_ daripada menggunakan _Query Builder_ dan query SQL mentah. Lebih memilih _collections_ daripada _array_](#lebih-memilih-menggunakan-eloquent-daripada-menggunakan-query-builder-dan-query-sql-mentah-lebih-memilih-collections-daripada-array) + +[_Mass assignment_](#mass-assignment) + +[Jangan mengeksekusi kueri dalam _template blade_ dan gunakan _eager loading_ (masalah N + 1)](#jangan-mengeksekusi-kueri-dalam-template-blade-dan-gunakan-eager-loading-masalah-n-1) + +[Komentari kode anda, tetapi lebih baik _method_ dan nama variabel yang deskriptif daripada komentar](#komentari-kode-anda-tetapi-lebih-baik-method-dan-nama-variabel-yang-deskriptif-daripada-komentar) + +[Jangan letakkan JS dan CSS di _template blade_ dan jangan letakkan HTML apa pun di kelas PHP](#jangan-letakkan-js-dan-css-di-template-blade-dan-jangan-letakkan-html-apa-pun-di-kelas-php) + +[Gunakan file _config_, _language_, dan konstanta daripada teks dalam kode](#gunakan-file-config-language-dan-konstanta-daripada-teks-dalam-kode) + +[Gunakan _tools_ standar Laravel yang diterima oleh komunitas](#gunakan-tools-standar-laravel-yang-diterima-oleh-komunitas) + +[Ikuti konvensi penamaan Laravel](#ikuti-konvensi-penamaan-laravel) + +[Gunakan sintaks yang lebih pendek dan lebih mudah dibaca jika memungkinkan](#gunakan-sintaks-yang-lebih-pendek-dan-lebih-mudah-dibaca-jika-memungkinkan) + +[Gunakan _IoC Container_ atau _facades_ daripada kelas baru](#gunakan-ioc-container-atau-facades-daripada-kelas-baru) + +[Jangan mendapatkan data dari file `.env` secara langsung](#jangan-mendapatkan-data-dari-file-env-secara-langsung) + +[Simpan tanggal dalam format standar. Gunakan _accessors_ dan _mutators_ untuk mengubah format tanggal](#simpan-tanggal-dalam-format-standar-gunakan-accessors-dan-mutators-untuk-mengubah-format-tanggal) + +[Praktik bagus lainnya](#praktik-bagus-lainnya) + +### **Prinsip _single responsibility_** + +Kelas dan metode seharusnya hanya memiliki satu tanggung jawab. + +Contoh buruk: + +```php +public function getFullNameAttribute(): string +{ + if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { + return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; + } else { + return $this->first_name[0] . '. ' . $this->last_name; + } +} +``` + +Contoh terbaik: + +```php +public function getFullNameAttribute(): string +{ + return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); +} + +public function isVerifiedClient(): bool +{ + return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); +} + +public function getFullNameLong(): string +{ + return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; +} + +public function getFullNameShort(): string +{ + return $this->first_name[0] . '. ' . $this->last_name; +} +``` + +[🔝 Kembali ke konten](#konten) + +### **Model tebal, _controller_ tipis** + +Masukkan semua logika terkait DB ke model _eloquent_ atau ke dalam kelas repositori jika anda menggunakan _Query Builder_ atau kueri SQL mentah. + +Contoh buruk: + +```php +public function index() +{ + $clients = Client::verified() + ->with(['orders' => function ($q) { + $q->where('created_at', '>', Carbon::today()->subWeek()); + }]) + ->get(); + + return view('index', ['clients' => $clients]); +} +``` + +Contoh terbaik: + +```php +public function index() +{ + return view('index', ['clients' => $this->client->getWithNewOrders()]); +} + +class Client extends Model +{ + public function getWithNewOrders() + { + return $this->verified() + ->with(['orders' => function ($q) { + $q->where('created_at', '>', Carbon::today()->subWeek()); + }]) + ->get(); + } +} +``` + +[🔝 Kembali ke konten](#konten) + +### **Validasi** + +Pindahkan validasi dari _controller_ ke kelas _request_. + +Contoh buruk: + +```php +public function store(Request $request) +{ + $request->validate([ + 'title' => 'required|unique:posts|max:255', + 'body' => 'required', + 'publish_at' => 'nullable|date', + ]); + + ... +} +``` + +Contoh terbaik: + +```php +public function store(PostRequest $request) +{ + ... +} + +class PostRequest extends Request +{ + public function rules() + { + return [ + 'title' => 'required|unique:posts|max:255', + 'body' => 'required', + 'publish_at' => 'nullable|date', + ]; + } +} +``` + +[🔝 Kembali ke konten](#konten) + +### **_Business logic_ harus di dalam kelas _services_** + +_Controller_ harus hanya memiliki satu tanggung jawab, jadi pindahkan _business logic_ dari _controller_ ke kelas _service_. + +Contoh buruk: + +```php +public function store(Request $request) +{ + if ($request->hasFile('image')) { + $request->file('image')->move(public_path('images') . 'temp'); + } + + ... +} +``` + +Contoh terbaik: + +```php +public function store(Request $request) +{ + $this->articleService->handleUploadedImage($request->file('image')); + + ... +} + +class ArticleService +{ + public function handleUploadedImage($image) + { + if (!is_null($image)) { + $image->move(public_path('images') . 'temp'); + } + } +} +``` + +[🔝 Kembali ke konten](#konten) + +### **_Don't repeat yourself_ (DRY)** + +Gunakan kembali kode ketika anda bisa. [PSR](#prinsip-single-responsibility) membantu anda menghindari duplikasi. Juga, gunakan kembali _template blade_, _scope eloquent_, dll. + +Contoh buruk: + +```php +public function getActive() +{ + return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); +} + +public function getArticles() +{ + return $this->whereHas('user', function ($q) { + $q->where('verified', 1)->whereNotNull('deleted_at'); + })->get(); +} +``` + +Contoh terbaik: + +```php +public function scopeActive($q) +{ + return $q->where('verified', 1)->whereNotNull('deleted_at'); +} + +public function getActive() +{ + return $this->active()->get(); +} + +public function getArticles() +{ + return $this->whereHas('user', function ($q) { + $q->active(); + })->get(); +} +``` + +[🔝 Kembali ke konten](#konten) + +### **Lebih memilih menggunakan _Eloquent_ daripada menggunakan _Query Builder_ dan query SQL mentah. Lebih memilih _collections_ daripada _array_** + +_Eloquent_ memungkinkan anda menulis kode yang dapat dibaca dan _maintainable_. Dan, _Eloquent_ memiliki _built-in tools_ yang bagus seperti _soft deletes_, _events_, _scopes_, dll. + +Contoh buruk: + +```sql +SELECT * +FROM `articles` +WHERE EXISTS (SELECT * + FROM `users` + WHERE `articles`.`user_id` = `users`.`id` + AND EXISTS (SELECT * + FROM `profiles` + WHERE `profiles`.`user_id` = `users`.`id`) + AND `users`.`deleted_at` IS NULL) +AND `verified` = '1' +AND `active` = '1' +ORDER BY `created_at` DESC +``` + +Contoh terbaik: + +```php +Article::has('user.profile')->verified()->latest()->get(); +``` + +[🔝 Kembali ke konten](#konten) + +### **_Mass assignment_** + +Contoh buruk: + +```php +$article = new Article; +$article->title = $request->title; +$article->content = $request->content; +$article->verified = $request->verified; + +// Add category to article +$article->category_id = $category->id; +$article->save(); +``` + +Contoh terbaik: + +```php +$category->article()->create($request->validated()); +``` + +[🔝 Kembali ke konten](#konten) + +### **Jangan mengeksekusi kueri dalam _template blade_ dan gunakan _eager loading_ (masalah N + 1)** + +Contoh buruk (untuk 100 _user_, 101 kueri DB akan dieksekusi): + +```blade +@foreach (User::all() as $user) + {{ $user->profile->name }} +@endforeach +``` + +Contoh terbaik (untuk 100 _user_, 2 kueri DB akan dieksekusi): + +```php +$users = User::with('profile')->get(); + +@foreach ($users as $user) + {{ $user->profile->name }} +@endforeach +``` + +[🔝 Kembali ke konten](#konten) + +### **Komentari kode anda, tetapi lebih baik _method_ dan nama variabel yang deskriptif daripada komentar** + +Contoh buruk: + +```php +if (count((array) $builder->getQuery()->joins) > 0) +``` + +Contoh lebih baik: + +```php +// Determine if there are any joins. +if (count((array) $builder->getQuery()->joins) > 0) +``` + +Contoh terbaik: + +```php +if ($this->hasJoins()) +``` + +[🔝 Kembali ke konten](#konten) + +### **Jangan letakkan JS dan CSS di _template blade_ dan jangan letakkan HTML apa pun di kelas PHP** + +Contoh buruk: + +```javascript +let article = `{{ json_encode($article) }}`; +``` + +Contoh lebih baik: + +```php + + +Atau + +