store/BEST_PRACTICE.md

181 lines
6.6 KiB
Markdown

# Best Practices
## PHP
### Traits
- Gabungkan multiple `use` trait menjadi satu baris dengan koma.
- **Hindari:**
```php
use FlashesEntityMessage;
use ParsesDataTableQuery;
```
- **Gunakan:**
```php
use FlashesEntityMessage, ParsesDataTableQuery;
```
## 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)