Design Patterns Sederhana
Design pattern adalah solusi yang sudah terbukti untuk masalah umum dalam programming. Kamu tidak perlu menghafal semuanya — cukup kenali yang paling sering dipakai di Laravel.
Di halaman ini, kita fokus pada 3 pattern paling penting: Singleton, Factory, dan Repository.
1. Singleton — "Cuma Boleh Satu"
Masalah
Beberapa objek seharusnya hanya ada satu instance di seluruh aplikasi. Contoh: koneksi database — kalau setiap request bikin koneksi baru, server bakal kehabisan resource.
Analogi
Bayangkan presiden negara. Tidak peduli berapa kali kamu panggil "Pak Presiden", yang datang selalu orang yang sama — tidak mungkin ada dua presiden sekaligus.
Implementasi
<?php
declare(strict_types=1);
class Database
{
private static ?Database $instance = null;
private PDO $pdo;
// Constructor private — tidak bisa "new Database()" dari luar
private function __construct()
{
$this->pdo = new PDO(
'mysql:host=localhost;dbname=belajar_php',
'root',
'',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
}
// Satu-satunya cara mendapatkan instance
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection(): PDO
{
return $this->pdo;
}
// Cegah cloning
private function __clone() {}
}
Penggunaan
<?php
// ❌ Tidak bisa: new Database() — constructor private
// ✅ Selalu pakai getInstance()
$db1 = Database::getInstance();
$db2 = Database::getInstance();
// $db1 dan $db2 adalah OBJEK YANG SAMA
var_dump($db1 === $db2); // true
// Query menggunakan koneksi singleton
$stmt = Database::getInstance()
->getConnection()
->query("SELECT * FROM users");
Di Laravel Nanti...
Laravel menggunakan pattern ini secara internal. Saat kamu menulis DB::connection(), Laravel memastikan hanya ada satu koneksi per database — ini Singleton pattern!
2. Factory — "Pabrik Pembuat Objek"
Masalah
Kadang proses membuat objek itu rumit — perlu validasi, konfigurasi, atau logika khusus. Kalau logika ini tersebar di mana-mana, maintenance akan sulit.
Analogi
Bayangkan pabrik roti. Kamu tidak perlu tahu cara kerja oven atau resep rahasia — cukup bilang "saya mau roti cokelat" dan pabrik akan mengurusnya.
Implementasi
<?php
declare(strict_types=1);
// Interface untuk semua notifikasi
interface Notifikasi
{
public function kirim(string $pesan): string;
}
class NotifEmail implements Notifikasi
{
public function __construct(
private string $emailTujuan,
) {}
public function kirim(string $pesan): string
{
return "📧 Email ke {$this->emailTujuan}: {$pesan}";
}
}
class NotifSMS implements Notifikasi
{
public function __construct(
private string $nomorHP,
) {}
public function kirim(string $pesan): string
{
return "📱 SMS ke {$this->nomorHP}: {$pesan}";
}
}
class NotifWhatsApp implements Notifikasi
{
public function __construct(
private string $nomorWA,
) {}
public function kirim(string $pesan): string
{
return "💬 WhatsApp ke {$this->nomorWA}: {$pesan}";
}
}
<?php
declare(strict_types=1);
// 🏭 Factory — Pabrik pembuat notifikasi
class NotifikasiFactory
{
public static function buat(string $tipe, string $tujuan): Notifikasi
{
return match ($tipe) {
'email' => new NotifEmail($tujuan),
'sms' => new NotifSMS($tujuan),
'whatsapp' => new NotifWhatsApp($tujuan),
default => throw new \InvalidArgumentException(
"Tipe notifikasi '{$tipe}' tidak dikenal"
),
};
}
}
Penggunaan
<?php
// ✅ Buat notifikasi lewat factory — simpel dan terpusat
$email = NotifikasiFactory::buat('email', '[email protected]');
$sms = NotifikasiFactory::buat('sms', '08123456789');
$wa = NotifikasiFactory::buat('whatsapp', '08198765432');
echo $email->kirim("Pesanan Anda telah dikirim");
// 📧 Email ke [email protected]: Pesanan Anda telah dikirim
echo $sms->kirim("Kode OTP: 123456");
// 📱 SMS ke 08123456789: Kode OTP: 123456
echo $wa->kirim("Halo, ada promo nih!");
// 💬 WhatsApp ke 08198765432: Halo, ada promo nih!
Keuntungan Factory:
- Logika pembuatan objek terpusat di satu tempat
- Menambah tipe baru cukup edit factory, bukan semua file
- Siapa yang memanggil tidak perlu tahu detail pembuatan objek
Di Laravel Nanti...
Laravel Factory dipakai untuk membuat data dummy:
// Buat 50 user fake untuk testing
User::factory()->count(50)->create();
3. Repository — "Gudang Data"
Masalah
Kalau query database tersebar di controller, akan sulit mencari dan mengubahnya. Repository pattern memisahkan logika akses data dari logika bisnis.
Analogi
Bayangkan pustakawan di perpustakaan. Kamu tidak langsung cari buku di rak — kamu bilang ke pustakawan "saya butuh buku tentang PHP" dan dia yang mencarikannya.
Implementasi
<?php
declare(strict_types=1);
// Interface Repository (kontrak)
interface UserRepositoryInterface
{
public function findById(int $id): ?array;
public function findByEmail(string $email): ?array;
public function all(): array;
public function create(array $data): int;
public function update(int $id, array $data): bool;
public function delete(int $id): bool;
}
// Implementasi dengan MySQL/PDO
class UserRepository implements UserRepositoryInterface
{
public function __construct(
private PDO $pdo,
) {}
public function findById(int $id): ?array
{
$stmt = $this->pdo->prepare(
"SELECT * FROM users WHERE id = ?"
);
$stmt->execute([$id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ?: null;
}
public function findByEmail(string $email): ?array
{
$stmt = $this->pdo->prepare(
"SELECT * FROM users WHERE email = ?"
);
$stmt->execute([$email]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ?: null;
}
public function all(): array
{
$stmt = $this->pdo->query(
"SELECT * FROM users ORDER BY created_at DESC"
);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function create(array $data): int
{
$stmt = $this->pdo->prepare(
"INSERT INTO users (name, email, password) VALUES (?, ?, ?)"
);
$stmt->execute([
$data['name'],
$data['email'],
password_hash($data['password'], PASSWORD_BCRYPT),
]);
return (int) $this->pdo->lastInsertId();
}
public function update(int $id, array $data): bool
{
$stmt = $this->pdo->prepare(
"UPDATE users SET name = ?, email = ? WHERE id = ?"
);
return $stmt->execute([$data['name'], $data['email'], $id]);
}
public function delete(int $id): bool
{
$stmt = $this->pdo->prepare("DELETE FROM users WHERE id = ?");
return $stmt->execute([$id]);
}
}
Penggunaan di Service Layer
<?php
declare(strict_types=1);
class UserService
{
public function __construct(
private UserRepositoryInterface $userRepo,
) {}
public function registerUser(array $data): int
{
// Validasi
if (empty($data['name']) || empty($data['email'])) {
throw new \InvalidArgumentException('Nama dan email wajib diisi');
}
// Cek duplikasi
$existing = $this->userRepo->findByEmail($data['email']);
if ($existing !== null) {
throw new \RuntimeException('Email sudah terdaftar');
}
// Simpan via repository
return $this->userRepo->create($data);
}
public function getUserProfile(int $id): array
{
$user = $this->userRepo->findById($id);
if ($user === null) {
throw new \RuntimeException("User #{$id} tidak ditemukan");
}
return $user;
}
}
<?php
// Setup
$pdo = new PDO('mysql:host=localhost;dbname=belajar_php', 'root', '');
$userRepo = new UserRepository($pdo);
$userService = new UserService($userRepo);
// Registrasi user baru
$userId = $userService->registerUser([
'name' => 'Budi',
'email' => '[email protected]',
'password' => 'secret123',
]);
echo "User baru ID: {$userId}\n";
// Ambil profil
$profile = $userService->getUserProfile($userId);
echo "Nama: {$profile['name']}\n";
Keuntungan Repository:
- Controller tetap bersih — tidak ada query SQL langsung
- Mudah diganti implementasinya (MySQL → PostgreSQL → API)
- Mudah di-test — bisa bikin
FakeUserRepository untuk testing
Di Laravel Nanti...
Eloquent Model di Laravel sebenarnya sudah bertindak seperti Repository:
$user = User::find(1); // findById
$users = User::all(); // all
$user = User::where('email', $email)->first(); // findByEmail
4. Rangkuman 3 Pattern
Pola Arsitektur di Laravel
Controller (terima request)
↓
Service (logika bisnis) ← Design Pattern di sini
↓
Repository (akses data)
↓
Database
Latihan
- Buat Singleton class
Config yang membaca file config.php dan menyediakan method get(string $key): mixed
- Buat Factory
ShapeFactory yang bisa membuat objek Circle, Rectangle, atau Triangle berdasarkan parameter string
- Buat Repository
ProductRepository dengan method findById(), findByCategory(), all(), dan create()
- Gabungkan: buat
ProductService yang menggunakan ProductRepository (injected via constructor)
Selanjutnya
Selamat! 🎉 Kamu sudah menguasai fondasi OOP yang dibutuhkan untuk Laravel:
- ✅ Class, Object, Property, Method
- ✅ Interface & Abstract Class
- ✅ Trait
- ✅ Namespace & Autoloading
- ✅ Design Patterns
Kamu sudah siap untuk mulai belajar Laravel secara mendalam. Kembali ke Pengenalan Laravel → dan mulai bangun aplikasi pertamamu dengan framework ini!