Design Patterns — PHP

Design patterns adalah solusi umum untuk masalah yang sering muncul dalam programming. Catatan Laravel: Laravel adalah framework PHP paling populer. Beberapa…

Design pattern adalah bentuk kode yang sudah terbukti untuk masalah desain yang sering muncul, lengkap dengan namanya. Dia bukan library yang di-install, tapi cara menyusun class. Bayangin kasir Warung Bu Tini. Pembeli bisa bayar tunai atau QRIS, dan besok mungkin transfer. Kasir yang baik gak perlu belajar ulang tiap ada cara bayar baru: dia cukup tahu "ini cara bayar, panggil bayar()". Lesson ini membahas lima pattern dalam bentuk PHP-nya. Penjelasan yang lebih dalam, satu pattern per lesson, ada di track Design Patterns.

🧪 Tebakan cepat: Koneksi::ambil() dipanggil dua kali. Apakah Koneksi::ambil() === Koneksi::ambil() menghasilkan true? Tebak dulu, jawabannya ada di bagian "Singleton: satu objek untuk semua".

Strategy: satu interface, banyak cara

Interface (dari OOP Lanjutan) menetapkan kontraknya, dan tiap cara bayar adalah class yang memenuhinya:

<?php
interface MetodeBayar {
    public function bayar(int $total): string;
}

class Tunai implements MetodeBayar {
    public function bayar(int $total): string {
        return "Tunai Rp$total, siapkan kembalian";
    }
}

class Qris implements MetodeBayar {
    public function bayar(int $total): string {
        return "QRIS Rp$total, tunggu notifikasi masuk";
    }
}

Dependency injection: Kasir menerima, bukan membuat

class Kasir {
    public function __construct(private MetodeBayar $metode) {}

    public function tutup(int $total): string {
        return $this->metode->bayar($total);
    }
}

$kasir = new Kasir(new Qris());
echo $kasir->tutup(20000);
QRIS Rp20000, tunggu notifikasi masuk

Open/Closed: tambah tanpa mengubah

Bu Tini mau menerima transfer. Caranya: tulis class Transfer implements MetodeBayar yang baru. Class Kasir gak disentuh sama sekali. Prinsip ini namanya Open/Closed Principle (OCP): class terbuka untuk ditambah kemampuannya, tapi tertutup untuk diubah isinya. Strategy dan DI adalah cara paling umum untuk mencapainya.

Factory: membuat objek dari sebuah kode

Dari form kasir yang datang cuma teks, misalnya "qris". Yang mengubahnya jadi objek adalah factory, satu tempat yang tahu semua class konkretnya:

class PembayaranFactory {
    public static function buat(string $kode): MetodeBayar {
        if ($kode === "tunai") {
            return new Tunai();
        }
        if ($kode === "qris") {
            return new Qris();
        }
        throw new InvalidArgumentException("Metode $kode belum ada");
    }
}

$kasir = new Kasir(PembayaranFactory::buat("qris"));

Singleton: satu objek untuk semua

class Koneksi {
    private static ?Koneksi $satu = null;

    private function __construct() {
        echo "Membuka koneksi ke database\n";
    }

    public static function ambil(): Koneksi {
        if (self::$satu === null) {
            self::$satu = new Koneksi();
        }
        return self::$satu;
    }
}

echo Koneksi::ambil() === Koneksi::ambil() ? "sama" : "beda";
Membuka koneksi ke database
sama

Singleton sekarang jarang dipakai. Dia sebenarnya variabel global: class mana pun bisa memanggil Koneksi::ambil() tanpa kelihatan dari constructor-nya, dan saat testing kamu gak bisa menukarnya dengan koneksi palsu. Framework seperti Laravel memberi "satu objek untuk semua" lewat service container ($this->app->singleton(...)), sementara class-nya tetap menerima objek itu lewat constructor seperti Kasir.

Repository: data di balik interface

Repository menyembunyikan dari mana data diambil di balik sebuah interface:

interface Repository {
    public function findAll(): array;
    public function findById(int $id): ?array;
}

class MenuRepository implements Repository {
    private array $data = [
        ["id" => 1, "nama" => "Nasi Goreng", "harga" => 15000],
        ["id" => 2, "nama" => "Es Teh", "harga" => 5000],
    ];

    public function findAll(): array {
        return $this->data;
    }

    public function findById(int $id): ?array {
        foreach ($this->data as $baris) {
            if ($baris["id"] === $id) {
                return $baris;
            }
        }
        return null;
    }
}

$repo = new MenuRepository();
echo count($repo->findAll()) . "\n";   // 2
echo $repo->findById(2)["nama"];       // Es Teh

⚠️ Jebakan yang sering ketemu

TL;DR: Design pattern adalah bentuk kode yang terbukti untuk masalah yang sering muncul. Strategy: beberapa class memenuhi satu interface, jadi bisa ditukar. Dependency injection: class menerima dependensinya lewat constructor, bukan membuatnya sendiri. Open/Closed Principle: tambah class baru tanpa mengubah class lama. Factory: satu tempat yang membuat objek dari sebuah kode dan melempar exception untuk kode yang gak dikenal. Singleton: satu instance lewat constructor private dan property static (=== bernilai true), sekarang jarang dipakai karena sulit dites. Repository: data di balik interface seperti findAll(): array dan findById(int $id): ?array, dan di Laravel perannya diambil Eloquent, ORM bawaan Laravel.

Yang akan kamu pelajari