Lewati ke konten utama

Action Pattern make:action - CQRS

Salah satu pondasi terpenting di starter kit ini adalah Action Pattern.

Kalau Anda baru belajar Laravel, anggap Action Pattern sebagai cara untuk memindahkan logika bisnis keluar dari controller, keluar dari Filament page, dan keluar dari tempat-tempat yang seharusnya fokus pada input/output.

Saya juga membuat command php artisan make:action karena Laravel belum menyediakan generator bawaan yang sesuai dengan pola kerja saya.

Kenapa Dokumen Ini Penting​

Di Laravel + Filament, kata "action" bisa membingungkan karena ada dua istilah yang mirip tetapi tugasnya berbeda:

  1. Action Pattern di app/Actions/...
  2. Filament Action seperti Action::make(), DeleteAction::make(), CreateAction::make()

Banyak developer pemula mencampuradukkan dua hal ini. Karena itu, dokumen ini wajib menjelaskan perbedaannya dengan jelas.

Apa Itu Action Pattern​

Action Pattern adalah class PHP yang menangani satu tugas bisnis dengan jelas.

Contoh:

  • CreateUserAction
  • UpdateUserAction
  • DeleteUserAction
  • ApproveOrderAction
  • PublishPostAction

Biasanya action pattern diletakkan di:

app/Actions/

Kenapa method handle()?​

Mungkin Anda bertanya: "Kenapa bukan execute(), run(), atau process()?".

Ada tiga alasan utama kenapa saya memilih handle() sebagai standar:

  1. Standar Laravel: Laravel menggunakan handle() untuk hampir semua hal yang bersifat "satuan tugas": Jobs, Commands, Middleware, dan Mailables. Menggunakan handle() membuat Action terasa seperti warga kelas satu (first-class citizen) di ekosistem Laravel.
  2. Dependency Injection: Jika Anda memanggil action menggunakan app(CreateUserAction::class)->handle($data), Laravel akan otomatis meng-inject dependency yang Anda butuhkan di constructor. Ini sangat kuat untuk pengujian (mocking).
  3. Keseragaman Dokumen: Dengan satu nama method yang pasti (handle), siapapun yang membaca kode Anda langsung tahu di mana "pintu masuk" utama dari logika bisnis tersebut tanpa harus mencari-cari nama method-nya.

Tips: Jika Anda ingin action yang bisa langsung dipanggil seperti function, Anda bisa menambahkan magic method __invoke(), tapi di starter kit ini saya menyarankan tetap eksplisit dengan handle().

Apa Itu Filament Action​

Filament Action adalah action milik UI Filament. Biasanya dipakai untuk:

  • tombol di header
  • tombol di tabel
  • tombol submit kecil di page
  • bulk action
  • modal action

Contoh:

Action::make('logoutOtherBrowserSessions')
DeleteAction::make()
CreateAction::make()
EditAction::make()

Filament Action berada di lapisan interface / presentasi, bukan lapisan business logic inti.

Perbedaan Action Pattern vs Filament Action​

AspekAction PatternFilament Action
Lokasiapp/Actions/...File Filament page/resource/table
TujuanMenjalankan business logicMenyediakan interaksi UI
Dipakai ulang dari API/CLI/JobYaTidak
Bergantung pada FilamentTidakYa
Cocok untuk transaksi bisnisYaHanya sebagai pemicu/orchestrator
Method umumhandle()Action::make(...)->action(...), ->using(...)

Cara Pikir yang Benar​

Filament Action = tombol atau trigger UI​

Ia mewakili "apa yang user klik".

Action Pattern = pekerjaan bisnis yang sesungguhnya​

Ia mewakili "apa yang sistem kerjakan".

Kalau dua hal ini dicampur, biasanya resource atau page akan cepat menjadi tebal dan sulit dipakai ulang.

Analogi Sederhana​

Bayangkan Anda punya tombol "Hapus User" di panel admin.

  • Filament Action adalah tombol hapus yang muncul di UI
  • DeleteUserAction adalah class yang benar-benar menjalankan proses hapus user

Artinya:

  • Filament Action menerima klik
  • Action Pattern menjalankan logika bisnis

Kenapa saya suka pattern ini​

Karena saya ingin:

  • logika create/update/delete tidak tersebar
  • kode mudah dipakai ulang dari Filament, API, command, atau job
  • tanggung jawab file lebih jelas
  • testing lebih mudah

Perbedaan Cara Pikir​

Tanpa Action Pattern, developer biasanya melakukan ini:

  • validasi di controller
  • mutasi model di controller
  • upload file di controller
  • kirim notifikasi di controller
  • transaksi database di controller
  • bahkan logic delete atau suspend user langsung ditulis di Filament page

Hasilnya controller atau Filament page jadi tebal, susah dites, dan susah dicari saat ada bug.

Dengan Action Pattern, alurnya berubah:

  1. UI menerima input
  2. UI atau request melakukan validasi dan authorization
  3. Action mengerjakan proses bisnis
  4. UI menampilkan hasil

Saya sengaja memakai method handle() di action supaya pola pemanggilannya konsisten di seluruh project.

Kapan Pakai Action Pattern​

Gunakan Action Pattern jika:

  • logika bisnis perlu dipakai ulang dari lebih dari satu tempat
  • operasi bukan sekadar klik UI, tetapi benar-benar mutasi data atau proses domain
  • Anda ingin logic mudah dites
  • operasi melibatkan transaksi, side effect, notifikasi, upload, atau update banyak model

Contoh yang cocok:

  • create user
  • update user
  • delete user
  • approve order
  • publish post
  • reset password user

Kapan Pakai Filament Action​

Gunakan Filament Action jika:

  • Anda butuh tombol atau action di halaman Filament
  • Anda ingin menampilkan modal, konfirmasi, atau trigger UI
  • action itu hidup di tabel, header, form, atau page Filament

Contoh yang cocok:

  • tombol DeleteAction::make()
  • tombol Action::make('savePassword')
  • tombol bulk action di table
  • action custom di halaman profile Filament

Kapan Pakai Keduanya Bersamaan​

Inilah pola yang paling sering saya pakai.

Gunakan:

  • Filament Action sebagai pemicu dari UI
  • Action Pattern sebagai pelaksana business logic

Ini biasanya adalah kombinasi terbaik.

Contoh Nyata: Pakai Action Pattern di Halaman Filament Resource​

Misalnya untuk halaman create user:

protected function handleRecordCreation(array $data): Model
{
return app(CreateUserAction::class)->handle($data);
}

Dan untuk edit user:

protected function handleRecordUpdate(Model $record, array $data): Model
{
return app(UpdateUserAction::class)->handle($record, $data);
}

Di sini:

  • halaman Filament tetap mengurus alur UI
  • business logic create/update dipindahkan ke action pattern

Contoh Nyata: Filament Delete Action Memanggil Action Pattern​

Di halaman edit atau tabel Filament, pola yang saya suka adalah seperti ini:

DeleteAction::make()
->using(fn (User $record, DeleteUserAction $deleteAction) => $deleteAction->handle($record)),

Di sini jelas sekali perannya:

  • DeleteAction::make() adalah Filament Action
  • DeleteUserAction adalah Action Pattern

Jadi, tombol delete tetap milik Filament, tetapi proses hapus user yang sebenarnya tetap dipusatkan di app/Actions/Users/DeleteUserAction.php.

Contoh Nyata: Filament Custom Action​

Untuk action UI custom, misalnya tombol logout browser lain di halaman profile, bentuk Filament Action bisa seperti ini:

Action::make('logoutOtherBrowserSessions')
->action(function (array $data, LogoutOtherBrowserSessionsAction $action): void {
$action->handle($data['password']);
})

Pola ini bagus karena:

  • tombol dan modal tetap ditangani Filament
  • logic logout tetap hidup di action pattern

Contoh Pemakaian di Controller API​

Di API controller, Action Pattern biasanya dipanggil setelah authorization dan validation selesai.

Contoh:

public function store(StoreUserRequest $request): JsonResponse
{
$user = app(CreateUserAction::class)->handle($request->validated());

return response()->json([
'message' => 'User berhasil dibuat',
'data' => [
'user' => $user,
],
'errors' => null,
], 201);
}

Di sini urutannya:

  1. StoreUserRequest mengurus authorization dan validation
  2. CreateUserAction mengurus business logic
  3. controller mengurus response JSON

Contoh Pemakaian di Controller API untuk Update​

public function update(UpdateUserRequest $request, User $user): JsonResponse
{
$user = app(UpdateUserAction::class)->handle($user, $request->validated());

return response()->json([
'message' => 'User berhasil diperbarui',
'data' => [
'user' => $user,
],
'errors' => null,
]);
}

Contoh Pemakaian di Controller API untuk Delete​

public function destroy(User $user): JsonResponse
{
app(DeleteUserAction::class)->handle($user);

return response()->json([
'message' => 'User berhasil dihapus',
'data' => null,
'errors' => null,
]);
}

Studi Kasus 1: Modul User​

Anda ingin membuat modul user yang bisa dipakai dari:

  • Filament admin panel
  • endpoint API
  • command impor data

Kalau logika create user ditaruh di masing-masing tempat, Anda akan menulis hal yang sama berulang kali:

  • hash password
  • set role default
  • kirim notifikasi
  • simpan avatar

Dengan CreateUserAction, semua itu bisa dipusatkan:

app(CreateUserAction::class)->handle($data);

Filament, API, dan command cukup memanggil action yang sama.

Studi Kasus 2: Aksi Bisnis Non-CRUD​

Contoh lain adalah ApproveOrderAction.

Ini bukan operasi CRUD biasa, tetapi tetap sangat cocok jadi action pattern karena:

  • punya satu tujuan jelas
  • dapat dipakai dari banyak tempat
  • mudah dites
  • tidak tergantung ke Filament

Kalau Anda butuh tombol approve di panel admin, maka:

  • tombol approve dibuat dengan Filament Action
  • proses approve tetap dijalankan oleh ApproveOrderAction

Fitur Utama Command​

  • Generate action class untuk Create / Update / Delete
  • Custom path atau sub-folder untuk struktur folder yang rapi
  • Interaktif jika tanpa parameter
  • Auto-create seluruh CRUD action jika tidak spesifik
  • Dukungan --force untuk overwrite file
  • Prompt konfirmasi jika file sudah ada dan tidak diberi --force

Penggunaan Dasar​

1. Generate Custom Action​

php artisan make:action

Maka akan muncul prompt:

What is the class name? (e.g., Posts/PublishPostAction):
> Posts/PublishPostAction

Hasilnya:

app/Actions/Posts/PublishPostAction.php

2. Generate CRUD Action untuk Model​

php artisan make:action --model=User

Hasil:

app/Actions/Users/CreateUserAction.php
app/Actions/Users/UpdateUserAction.php
app/Actions/Users/DeleteUserAction.php

3. Custom Sub-Folder​

php artisan make:action --model=User --sub-folder=Access

Hasil:

app/Actions/Access/CreateUserAction.php
app/Actions/Access/UpdateUserAction.php
app/Actions/Access/DeleteUserAction.php

4. Generate Spesifik Action​

php artisan make:action --model=User --create
php artisan make:action --model=User --update
php artisan make:action --model=User --delete

5. Overwrite File dengan Paksa​

php artisan make:action --model=User --force

Tanpa --force, jika file sudah ada maka akan muncul prompt konfirmasi.

Stub File​

Stub disimpan di:

stubs/
├── create-action.stub
├── update-action.stub
├── delete-action.stub
└── custom-action.stub

Jika tidak ditemukan, command akan menggunakan fallback stub bawaan dari path internal command.

Argument dan Opsi​

OpsiAliasKeterangan
name-Nama action class, dipakai untuk custom action
--model=Post-mNama model yang akan dipakai untuk generate action CRUD
--create-cHanya generate Create<Model>Action
--update-uHanya generate Update<Model>Action
--delete-dHanya generate Delete<Model>Action
--sub-folder=XYZ-sSub-folder dalam app/Actions
--force-fOverwrite file jika sudah ada

Struktur Folder Hasil​

app/
└── Actions/
├── Users/
│ ├── CreateUserAction.php
│ ├── UpdateUserAction.php
│ └── DeleteUserAction.php
└── Posts/
└── PublishPostAction.php

Best Practice​

  • Gunakan --sub-folder untuk struktur domain-based seperti Billing, Auth, atau Access
  • Simpan action yang bukan CUD di folder yang menjelaskan domain-nya
  • Pisahkan validasi dan otorisasi ke FormRequest, Policy, atau Filament action
  • Gunakan handle() secara konsisten agar pola class mudah dikenali
  • Jika action memodifikasi beberapa model sekaligus, pertimbangkan DB::transaction()
  • Pakai Filament Action untuk UI trigger, dan Action Pattern untuk business logic

Kesalahan Umum Developer Pemula​

1. Menaruh authorization di dalam Action Pattern​

Saya tidak menyarankan ini. Action harus fokus pada logika bisnis. Authorization sebaiknya dilakukan oleh pemanggil.

2. Menaruh semua logika di Filament Resource​

Ini cepat di awal, tapi menyulitkan ketika logic mulai dipakai ulang.

3. Mengira Filament Action dan Action Pattern itu sama​

Ini kesalahan yang sangat umum.

  • Filament Action = lapisan UI
  • Action Pattern = lapisan business logic

4. Membuat action terlalu generik​

Contoh yang kurang bagus:

  • SaveUserAction
  • ProcessDataAction

Nama seperti ini sering terlalu kabur. Saya lebih suka nama yang jelas tujuannya:

  • CreateUserAction
  • UpdateUserAction
  • AssignRoleToUserAction

Kesimpulan​

Kalau disederhanakan:

  • Filament Action dipakai untuk interaksi di panel admin
  • Action Pattern dipakai untuk pekerjaan bisnis yang sesungguhnya
  • Dalam banyak kasus, keduanya dipakai bersama

Action Pattern adalah salah satu cara utama saya menjaga starter kit ini tetap rapi saat project bertambah besar. Kalau Anda paham file ini, Anda akan jauh lebih mudah memahami file dokumentasi lain di starter kit ini.