9.7 KiB
Best Practices
PHP
Traits
- Gabungkan multiple
usetrait menjadi satu baris dengan koma. - Hindari:
use FlashesEntityMessage; use ParsesDataTableQuery; - Gunakan:
use FlashesEntityMessage, ParsesDataTableQuery;
Model Eloquent
-
Urutan Penulisan di dalam Model Class: Setiap model harus disusun mengikuti urutan struktur berikut dari atas ke bawah:
- Use Trait (misal:
use HasFactory, SoftDeletes;). - Casting (metode
casts()). - Relasi (Relationships) (diurutkan secara alfabetis berdasarkan nama method relasi).
- Attribute (Accessors / Appends) (misal format harga, format tanggal, dll. Harus dimasukkan dalam array
$appendsdi atas class). - Scope (metode local scope dengan PHP attribute
#[Scope]). - Method Lainnya (helper method, logic bisnis, dll.).
- Use Trait (misal:
-
Hubungan Timbal Balik (2-Way Relations):
- Pastikan setiap relasi ditulis secara 2 arah (bi-directional).
- Jika suatu model memiliki
belongsToke model lain, pastikan model lain tersebut juga mendefinisikan relasi kebalikannya (hasManyatauhasOne).
-
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$appendsmodel.
-
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.
- Jika model memiliki opsi/status tertentu, buatlah local scope agar kueri dapat digunakan kembali (reusable). Gunakan PHP attribute
-
Contoh:
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
: voidsecara eksplisit pada methodup()dandown(). - Urutan & Pengelompokan Kolom (Ordering & Grouping):
- Primary Key:
$table->id()ditaruh paling atas. - Relasi (Foreign Keys / Morphs): Ditaruh tepat setelah Primary Key di bagian teratas.
- Kolom Data Utama: Dikelompokkan secara logis menggunakan spasi (baris kosong) sebagai pemisah antar kelompok data.
- Timestamps & Soft Deletes: Ditaruh paling bawah.
- Primary Key:
- Gunakan:
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
timestampeksplisit dengan defaultuseCurrent()danuseCurrentOnUpdate(). Hindari penggunaan$table->timestamps()bawaan Laravel agar format kolom presisi dan konsisten. - Tambahkan
$table->softDeletes()(ataudeleted_at) jika model terkait menggunakan traitSoftDeletes.$table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); $table->softDeletes();
- Gunakan
- Kolom Enum:
- Hubungkan dengan PHP Backed Enum menggunakan
array_column(EnumName::cases(), 'value'). - Contoh:
$table->enum('status', array_column(StatusEnum::cases(), 'value'))->default(StatusEnum::PENDING->value);
- Hubungkan dengan PHP Backed Enum menggunakan
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(), ataurestrictOnDelete()) sesuai kebutuhan integritas data. - Contoh:
$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: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. GunakandropConstrainedForeignIdataudropForeignuntuk cara yang aman dan ringkas.- Gunakan:
public function down(): void { Schema::table('table_name', function (Blueprint $table) { $table->dropConstrainedForeignId('foreign_key_id'); $table->dropColumn(['column_one', 'column_two']); }); }
- Gunakan:
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:
import CategoryFormModal from '@/components/admin/master/categories/CategoryFormModal.vue'; - Gunakan:
// 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.vueContoh: 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.vueContoh: raw-materials (nested prices, multi-image upload, dynamic variant rows)