Pendahuluan — penjaga sudah ada, siapa yang mengatur alur?
Di Request & Form Request (#57) kamu sudah punya penjaga input. Artikel ini adalah #58 (ini) — langkah ketiga stack Laravel di Seri 4.
Ide barunya: setelah data lolos penjaga, kode jangan menumpuk di satu tempat. Kita membagi peran: pengatur kode (controller) mengatur alur, layanan (service) mengerjakan langkah kerja (sering disebut logika bisnis), dan Eloquent sebagai pintu ke tabel database.
Awam: bayangkan perpustakaan. Petugas loket (penjaga) cek formulir. Manajer loket (controller) bilang “simpan buku ini”. Staf rak (service) menyusun langkah kerja. Buku lalu masuk kartu katalog (Eloquent / tabel) — bukan ditulis di kertas acak di meja loket.
Prasyarat: sudah baca Request & Form Request (#57) — paham validasi, status 422/201. Domain tetap perpustakaan mini. Pakai Laravel 11+ — pola controller/service di sini berlaku di versi modern.
Istilah — Controller, Service, Eloquent
| Istilah | Arti awam | Contoh singkat |
|---|---|---|
| Controller | Pengatur kode: terima request bersih, panggil layanan, kembalikan JSON | BukuController::store |
| Service | Layanan: tempat langkah kerja (“tambah buku ke koleksi”) | BukuService::tambah |
| Eloquent | Cara Laravel bicara ke tabel database lewat kelas Model | Buku::create(...) |
| Model | Kelas yang mewakili satu jenis data (misalnya baris di tabel buku) | class Buku extends Model |
Jangan hafal semua dulu. Cukup ingat: controller mengatur, service mengerjakan, Eloquent menyimpan/membaca.
Kenapa belum langsung Eloquent?
Kenapa belum langsung model Laravel? Karena ide “pisah peran” bisa dirasakan di PHP biasa dengan array sebagai “rak sementara”. Kalau ide-nya sudah “klik”, cuplikan Laravel nanti terasa seperti bungkus yang sama — hanya penyimpanannya diganti tabel sungguhan.
<?php
// Rak sementara di memori (bukan database).
$rak = [];
class BukuService
{
public function __construct(private array &$rak)
{
}
public function tambah(array $data): array
{
$id = count($this->rak) + 1;
$buku = [
"id" => $id,
"judul" => $data["judul"],
"tahun" => (int) $data["tahun"],
];
$this->rak[] = $buku;
return $buku;
}
}
// Pengatur kode tipis: terima data bersih -> panggil layanan -> jawab JSON.
function simpanBuku(array $data, BukuService $layanan): void
{
$buku = $layanan->tambah($data);
header("Content-Type: application/json; charset=utf-8");
http_response_code(201);
echo json_encode(["ok" => true, "buku" => $buku], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;
}
$layanan = new BukuService($rak);
simpanBuku(["judul" => "Belajar PHP", "tahun" => 2024], $layanan);
Output:
{
"ok": true,
"buku": {
"id": 1,
"judul": "Belajar PHP",
"tahun": 2024
}
}
Awam: fungsi simpanBuku seperti controller mini. Kelas BukuService mengerjakan “cara menambah buku”. Array $rak sementara menggantikan tabel — nanti diganti Eloquent.
Baca daftar — service yang sama
Menambah dan membaca memakai layanan yang sama, supaya aturan “cara kerja buku” tidak tersebar:
<?php
$rak = [
["id" => 1, "judul" => "Belajar PHP", "tahun" => 2024],
];
class BukuService
{
public function __construct(private array &$rak)
{
}
public function semua(): array
{
return $this->rak;
}
public function cari(int $id): ?array
{
foreach ($this->rak as $buku) {
if ((int) $buku["id"] === $id) {
return $buku;
}
}
return null;
}
}
$layanan = new BukuService($rak);
header("Content-Type: application/json; charset=utf-8");
echo json_encode(["data" => $layanan->semua()], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;
Output:
{
"data": [
{
"id": 1,
"judul": "Belajar PHP",
"tahun": 2024
}
]
}
Awam: kalau nanti ganti array jadi tabel, cukup ubah isi service — controller tetap “panggil layanan, balas JSON”.
Laravel — Controller tipis
Di project Laravel, cuplikan tipikal memindahkan alur ke kelas controller. File ini bukan dijalankan dengan php file.php — ia hidup di dalam project Laravel:
<?php
// Cuplikan Laravel (bukan file mandiri) — pengatur kode.
namespace App\Http\Controllers;
use App\Http\Requests\StoreBukuRequest;
use App\Services\BukuService;
use Illuminate\Http\JsonResponse;
class BukuController extends Controller
{
public function __construct(private BukuService $layanan)
{
}
public function store(StoreBukuRequest $request): JsonResponse
{
$buku = $this->layanan->tambah($request->validated());
return response()->json(['ok' => true, 'buku' => $buku], 201);
}
}
Awam:
StoreBukuRequest= penjaga dari Request & Form Request (#57) — data sudah dicekvalidated()= ambil hanya isian yang sudah lolos penjagaBukuService= layanan yang tahu cara menambah bukuprivate BukuService $layanandi konstruktor = Laravel menyiapkan layanan otomatis (kamu tidak perlunewmanual di sini)JsonResponse= tipe jawaban “ini JSON” (boleh diabaikan dulu kalau masih asing)- Controller hanya mengatur: terima -> panggil layanan -> JSON
Route biasanya mengarah ke method controller, misalnya POST /api/buku -> BukuController::store. Pintu HTTP tetap seperti di Laravel Routing & JSON (#56) — yang berubah: isi pintu sekarang memanggil pengatur kode, bukan menumpuk semua logika di satu file route.
Laravel — Service & Eloquent
Layanan memakai Model Eloquent untuk menulis ke tabel. Anggap Model seperti “kartu katalog” untuk satu jenis data:
<?php
// Cuplikan Laravel — Model (pintu ke tabel buku).
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Buku extends Model
{
protected $fillable = ['judul', 'tahun'];
}
<?php
// Cuplikan Laravel — layanan memakai Eloquent.
namespace App\Services;
use App\Models\Buku;
class BukuService
{
public function tambah(array $data): Buku
{
return Buku::create([
'judul' => $data['judul'],
'tahun' => (int) $data['tahun'],
]);
}
public function semua()
{
return Buku::query()->orderBy('id')->get();
}
}
Awam: Buku::create(...) artinya “buat baris baru di tabel buku”. $fillable = daftar kolom/isian yang boleh diisi lewat create (supaya tidak sembarang data ikut masuk). Buku::query()->orderBy('id')->get() artinya “ambil semua buku, urutkan menurut id”. Tabel sungguhan biasanya dibuat lewat migrasi (skrip pembuat tabel) — detailnya bisa dipelajari nanti; di sini cukup paham Model sebagai pintu.
Pola Dasar — Controller, Service, Eloquent
-
1
Pastikan penjaga sudah berdiri Validasi dulu (lihat Request & Form Request (#57)) — baru susun alur.
-
2
Buat controller tipis Terima request bersih, panggil layanan, kembalikan JSON + status.
-
3
Pindahkan langkah kerja ke service “Tambah buku”, “cari buku”, “daftar buku” tinggal di satu tempat.
-
4
Simpan lewat Eloquent Model +
create/query— bukan SQL panjang di controller. -
5
Baru pikir siapa yang boleh masuk Berikutnya: otentikasi (siapa yang login) — setelah alur simpan sudah rapi.
Kode lengkap — demo mandiri
Simpan sebagai laravel_controller_service_demo.php, lalu jalankan php laravel_controller_service_demo.php:
<?php
declare(strict_types=1);
$rak = [];
class BukuService
{
public function __construct(private array &$rak)
{
}
public function tambah(array $data): array
{
$id = count($this->rak) + 1;
$buku = [
"id" => $id,
"judul" => (string) $data["judul"],
"tahun" => (int) $data["tahun"],
];
$this->rak[] = $buku;
return $buku;
}
public function semua(): array
{
return $this->rak;
}
}
function simpanBuku(array $data, BukuService $layanan): array
{
$buku = $layanan->tambah($data);
return ["status" => 201, "body" => ["ok" => true, "buku" => $buku]];
}
function daftarBuku(BukuService $layanan): array
{
return ["status" => 200, "body" => ["data" => $layanan->semua()]];
}
function demo(string $judul, callable $aksi): void
{
echo "=== {$judul} ===", PHP_EOL;
$hasil = $aksi();
echo "status: ", $hasil["status"], PHP_EOL;
echo json_encode($hasil["body"], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL, PHP_EOL;
}
$layanan = new BukuService($rak);
demo("POST bersih -> 201", function () use ($layanan) {
return simpanBuku(["judul" => "Belajar PHP", "tahun" => 2024], $layanan);
});
demo("GET daftar -> 200", function () use ($layanan) {
return daftarBuku($layanan);
});
Awam: demo(...) hanya membungkus output agar mudah dibaca di terminal — bukan fitur Laravel. callable artinya “sesuatu yang bisa dipanggil seperti fungsi”. Baris declare(strict_types=1); membuat tipe data lebih ketat — boleh diikuti, tidak wajib dihafal dulu.
Kesalahan umum
| Gejala | Penyebab tipikal | Perbaikan awam |
|---|---|---|
| Controller membengkak | Langkah kerja ditulis di controller | Pindahkan ke service |
| Perintah database tersebar di banyak file | Eloquent/query dipanggil dari mana-mana | Satukan lewat service |
| Data kotor ikut tersimpan | Melewati penjaga | Pakai Form Request dulu (Request & Form Request) |
| Isian tidak tersimpan / error “kolom tidak boleh diisi massal” | Kolom belum ada di $fillable |
Tambahkan nama kolom ke $fillable |
Latihan singkat
- Ubah demo: tambah method
cari(int $id)di service, lalu buat fungsi pengatur yang mengembalikan 404 jika tidak ketemu. - Di cuplikan Laravel, bayangkan route
POST /api/bukumengarah ke methodstorediBukuController— tulis dalam satu kalimat alur dari request sampai JSON 201. - Jelaskan ke teman (tanpa jargon): beda controller vs service vs Eloquent dengan analogi loket perpustakaan.
FAQ singkat
Apa bedanya controller dan service?
Controller mengatur alur (siapa dipanggil, apa yang dikembalikan). Service mengerjakan langkah kerja. Awam: manajer loket vs staf yang menyusun rak.
Haruskah selalu pakai service?
Untuk API kecil, kadang controller langsung ke Eloquent masih oke. Begitu aturan bertambah (cek stok, hitung denda, kirim notifikasi), service membantu tetap rapi.
Eloquent wajib dari awal?
Ide penyimpanan bisa dilatih dengan array dulu. Eloquent dipakai saat data perlu bertahan di database sungguhan.
Lanjut ke mana?
Berikutnya: otentikasi — siapa yang boleh memanggil API (login / “bukti masuk”). Setelah alur simpan rapi, baru kunci pintu.
Kesimpulan
Kamu sudah memisahkan peran: controller mengatur, service mengerjakan, Eloquent menyimpan/membaca. Penjaga dari Request & Form Request (#57) tetap di depan — baru data bersih masuk alur ini.
Seri 4 progress: langkah #58 (ini) · 6/8 menuju Capstone Laravel · stack Laravel 3/5 · prasyarat: Request & Form Request (#57) LIVE. Berikutnya: otentikasi API (siapa yang boleh masuk).