# Best Practices ## PHP ### Traits - Gabungkan multiple `use` trait menjadi satu baris dengan koma. - **Hindari:** ```php use FlashesEntityMessage; use ParsesDataTableQuery; ``` - **Gunakan:** ```php use FlashesEntityMessage, ParsesDataTableQuery; ``` ### Model Eloquent - **Urutan Penulisan di dalam Model Class:** Setiap model harus disusun mengikuti urutan struktur berikut dari atas ke bawah: 1. **Use Trait** (misal: `use HasFactory, SoftDeletes;`). 2. **Casting** (metode `casts()`). 3. **Relasi (Relationships)** (diurutkan secara alfabetis berdasarkan nama method relasi). 4. **Attribute (Accessors / Appends)** (misal format harga, format tanggal, dll. Harus dimasukkan dalam array `$appends` di atas class). 5. **Scope** (metode local scope dengan PHP attribute `#[Scope]`). 6. **Method Lainnya** (helper method, logic bisnis, dll.). - **Hubungan Timbal Balik (2-Way Relations):** - Pastikan setiap relasi ditulis secara 2 arah (bi-directional). - Jika suatu model memiliki `belongsTo` ke model lain, pastikan model lain tersebut juga mendefinisikan relasi kebalikannya (`hasMany` atau `hasOne`). - **Casting & Formatting Attribute:** - Lakukan casting secara tepat pada kolom yang memerlukan tipe data khusus (seperti Boolean, Integer, Datetime, atau Enum). - Jika suatu kolom perlu diformat (misalnya konversi harga ke format Rupiah atau format tanggal lokal), buatlah accessor menggunakan class `Attribute` (Eloquent Attribute) dan tambahkan nama atribut tersebut ke properti `$appends` model. - **Query Scopes:** - Jika model memiliki opsi/status tertentu, buatlah local scope agar kueri dapat digunakan kembali (reusable). Gunakan PHP attribute `#[Scope]` di atas metode scope. - **Contoh:** ```php namespace App\Models; use App\Enums\OrderStatus; use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; #[Guarded(['id'])] #[Appends(['amount_formatted', 'status_label'])] class Order extends Model { // 1. Use Trait use HasFactory, SoftDeletes; // 2. Casting protected function casts(): array { return [ 'status' => OrderStatus::class, 'amount' => 'integer', ]; } // 3. Relasi (Urut Abjad) public function customer(): BelongsTo { return $this->belongsTo(Customer::class); } public function items(): HasMany { return $this->hasMany(OrderItem::class); } // 4. Attribute public function amountFormatted(): Attribute { return Attribute::make( get: fn () => 'Rp '.number_format($this->amount, 0, ',', '.'), ); } public function statusLabel(): Attribute { return Attribute::make( get: fn () => $this->status->label(), ); } // 5. Scope #[Scope] public function pending(Builder $query): void { $query->where('status', OrderStatus::PENDING->value); } } ``` ## Database & Migrasi ### Struktur File Migrasi - Gunakan *anonymous class* yang meng-extend `Migration`. - Tulis *return type hint* `: void` secara eksplisit pada method `up()` dan `down()`. - **Urutan & Pengelompokan Kolom (Ordering & Grouping):** 1. **Primary Key**: `$table->id()` ditaruh paling atas. 2. **Relasi (Foreign Keys / Morphs)**: Ditaruh tepat setelah Primary Key di bagian teratas. 3. **Kolom Data Utama**: Dikelompokkan secara logis menggunakan spasi (baris kosong) sebagai pemisah antar kelompok data. 4. **Timestamps & Soft Deletes**: Ditaruh paling bawah. - **Gunakan:** ```php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('table_name', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->foreignId('category_id')->nullable()->constrained()->nullOnDelete(); $table->string('name', 100); $table->string('description', 255)->nullable(); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); $table->softDeletes(); }); } public function down(): void { Schema::dropIfExists('table_name'); } }; ``` ### Definisi Kolom & Tipe Data - **ID & Timestamps:** - Gunakan `$table->id()` sebagai primary key. - Gunakan `timestamp` eksplisit dengan default `useCurrent()` dan `useCurrentOnUpdate()`. **Hindari penggunaan `$table->timestamps()` bawaan Laravel agar format kolom presisi dan konsisten.** - Tambahkan `$table->softDeletes()` (atau `deleted_at`) jika model terkait menggunakan trait `SoftDeletes`. ```php $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); $table->softDeletes(); ``` - **Kolom Enum:** - Hubungkan dengan PHP Backed Enum menggunakan `array_column(EnumName::cases(), 'value')`. - **Contoh:** ```php $table->enum('status', array_column(StatusEnum::cases(), 'value'))->default(StatusEnum::PENDING->value); ``` ### Relasi & Foreign Key - Gunakan `$table->foreignId()`. - Tentukan constraint relasi secara eksplisit dengan `constrained()` (dan sebutkan nama tabel target jika nama kolom tidak sesuai dengan nama tabel, misal: `constrained('users')`). - Tentukan aksi penghapusan secara eksplisit (`cascadeOnDelete()`, `nullOnDelete()`, atau `restrictOnDelete()`) sesuai kebutuhan integritas data. - **Contoh:** ```php $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete(); ``` ### Modifikasi Tabel (Alter Schema) - Saat menambahkan kolom baru, gunakan modifier `->after('column_name')` agar tata letak kolom di database terstruktur dengan logis. - Jika menambahkan kolom baru yang non-nullable pada tabel yang sudah berisi data, jalankan data migration di dalam method `up()` setelah schema builder: ```php DB::table('table_name')->update(['column_name' => 'default_value']); ``` - Di dalam method `down()`, pastikan untuk melepas constraint foreign key terlebih dahulu sebelum menghapus kolom bersangkutan. Gunakan `dropConstrainedForeignId` atau `dropForeign` untuk cara yang aman dan ringkas. - **Gunakan:** ```php public function down(): void { Schema::table('table_name', function (Blueprint $table) { $table->dropConstrainedForeignId('foreign_key_id'); $table->dropColumn(['column_one', 'column_two']); }); } ``` ## JavaScript / Vue ### Struktur Folder Page-Specific Components Komponen yang hanya digunakan oleh satu page tertentu (misalnya modal form, kolom tabel, action button) **harus ditempatkan di folder page-nya langsung** di dalam subfolder sesuai fungsinya, bukan di `resources/js/components/`. Folder `resources/js/components/` hanya untuk komponen yang **reusable** lintas page (seperti UI primitives, DataTable, ConfirmDialog, dll). Subfolder di dalam page folder dikelompokkan berdasarkan fungsi: - `form/` — modal form, form fields - `table/` — columns definition, data-table-actions, cell renderers - **Hindari:** ``` resources/js/components/admin/master/categories/ CategoryFormModal.vue columns.ts data-table-actions.vue resources/js/pages/admin/master/categories/ Index.vue ``` - **Gunakan:** ``` resources/js/pages/admin/master/categories/ Index.vue form/ CategoryFormModal.vue table/ columns.ts data-table-actions.vue ``` ### Import Path Gunakan relative import (`./`) untuk file yang berada di folder/subfolder yang sama, bukan absolute path `@/...`. - **Hindari:** ```ts import CategoryFormModal from '@/components/admin/master/categories/CategoryFormModal.vue'; ``` - **Gunakan:** ```ts // Dari Index.vue ke subfolder import CategoryFormModal from './form/CategoryFormModal.vue'; import { createColumns } from './table/columns'; // Dari columns.ts ke file satu folder import DataTableActions from './data-table-actions.vue'; ``` ### Pola CRUD (Create/Edit) Gunakan **modal** untuk CRUD yang sederhana (sedikit field, tidak ada nested data complex), dan **page terpisah** untuk CRUD yang kompleks. - **Modal** — form sederhana, biasanya 1-3 field, tidak memerlukan layout khusus: ``` pages/admin/master/categories/ Index.vue form/ CategoryFormModal.vue ← modal form table/ columns.ts data-table-actions.vue ``` Contoh: categories (1 field), suppliers (3 field), customers (3 field) - **Page terpisah** — form kompleks, banyak field, nested data, file upload, atau memerlukan layout khusus: ``` pages/admin/master/raw-materials/ Index.vue Create.vue ← halaman tambah Edit.vue ← halaman ubah form/ RawMaterialForm.vue ← form component table/ RawMaterialGroupedTable.vue data-table-actions.vue raw-material-status-toggle.vue ``` Contoh: raw-materials (nested prices, multi-image upload, dynamic variant rows)