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.

Request bersih -> controller -> service -> simpan Request sudah lolos penjaga Controller atur alur Service langkah kerja Eloquent / rak simpan & baca
Controller tidak “menyimpan sendiri”. Ia mengatur siapa yang bekerja — supaya file tetap tipis dan mudah dibaca.

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 dicek
  • validated() = ambil hanya isian yang sudah lolos penjaga
  • BukuService = layanan yang tahu cara menambah buku
  • private BukuService $layanan di konstruktor = Laravel menyiapkan layanan otomatis (kamu tidak perlu new manual 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. 1
    Pastikan penjaga sudah berdiri Validasi dulu (lihat Request & Form Request (#57)) — baru susun alur.
  2. 2
    Buat controller tipis Terima request bersih, panggil layanan, kembalikan JSON + status.
  3. 3
    Pindahkan langkah kerja ke service “Tambah buku”, “cari buku”, “daftar buku” tinggal di satu tempat.
  4. 4
    Simpan lewat Eloquent Model + create / query — bukan SQL panjang di controller.
  5. 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

  1. Ubah demo: tambah method cari(int $id) di service, lalu buat fungsi pengatur yang mengembalikan 404 jika tidak ketemu.
  2. Di cuplikan Laravel, bayangkan route POST /api/buku mengarah ke method store di BukuController — tulis dalam satu kalimat alur dari request sampai JSON 201.
  3. 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).