Kuasai OOP di PHP dengan contoh praktis. Pelajari kelas, pewarisan, polimorfisme, enkapsulasi, dan pola desain yang diandalkan sistem produksi.

Pemrograman Berorientasi Objek (OOP) adalah tulang punggung aplikasi PHP modern. Baik Anda membangun aplikasi Laravel, memelihara kode warisan, atau merancang microservices, memahami prinsip OOP adalah keharusan.
Kemampuan OOP PHP telah berkembang signifikan sejak PHP 5. Saat ini, PHP 8+ menawarkan fitur-fitur tangguh seperti typed properties, constructor property promotion, dan named arguments yang membuat pola OOP lebih ekspresif dan mudah dirawat.
Panduan ini mencakup lima konsep OOP inti dengan contoh tingkat produksi yang dapat Anda gunakan segera dalam proyek Anda.
Kelas adalah cetak biru. Objek adalah instansi dari cetak biru tersebut. Pikirkan kelas sebagai pemotong kue dan objek sebagai kue yang sebenarnya.
class User
{
public string $name;
public string $email;
private string $password;
public function __construct(string $name, string $email, string $password)
{
$this->name = $name;
$this->email = $email;
$this->password = $password;
}
public function getEmail(): string
{
return $this->email;
}
private function hashPassword(string $password): string
{
return password_hash($password, PASSWORD_BCRYPT);
}
}
// Membuat objek
$user = new User('John Doe', 'john@example.com', 'secret123');
echo $user->name; // John DoeDi PHP 8+, Anda dapat menggunakan constructor property promotion untuk mengurangi boilerplate:
class User
{
public function __construct(
public string $name,
public string $email,
private string $password,
) {}
}
$user = new User('John Doe', 'john@example.com', 'secret123');Ini secara otomatis mendeklarasikan dan menetapkan properti dalam konstruktor, membuat kode lebih bersih dan mudah dibaca.
Enkapsulasi adalah tentang menyembunyikan detail internal dan hanya mengekspos apa yang diperlukan. Ini melindungi keadaan objek Anda dari gangguan eksternal yang tidak diinginkan.
PHP menyediakan tiga modifier visibilitas:
public: Dapat diakses dari mana sajaprotected: Dapat diakses dalam kelas dan subkelasnyaprivate: Hanya dapat diakses dalam kelasclass BankAccount
{
private float $balance = 0;
private string $accountNumber;
public function __construct(string $accountNumber)
{
$this->accountNumber = $accountNumber;
}
public function deposit(float $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException('Jumlah setoran harus positif');
}
$this->balance += $amount;
}
public function withdraw(float $amount): void
{
if ($amount > $this->balance) {
throw new Exception('Saldo tidak cukup');
}
$this->balance -= $amount;
}
public function getBalance(): float
{
return $this->balance;
}
public function getAccountNumber(): string
{
return $this->accountNumber;
}
}
$account = new BankAccount('ACC123456');
$account->deposit(1000);
$account->withdraw(200);
echo $account->getBalance(); // 800
// Ini akan gagal - balance bersifat private
// $account->balance = 5000; // Error: Cannot access private propertyMengapa enkapsulasi penting: Anda mengontrol bagaimana data berubah. Dalam contoh di atas, Anda mencegah setoran negatif dan overdraft di sumbernya, bukan dalam kode pemanggil.
Pewarisan memungkinkan kelas untuk mewarisi properti dan metode dari kelas lain. Ini mempromosikan penggunaan kembali kode dan membangun hubungan hierarki.
class Animal
{
protected string $name;
protected int $age;
public function __construct(string $name, int $age)
{
$this->name = $name;
$this->age = $age;
}
public function eat(): string
{
return "{$this->name} sedang makan";
}
public function sleep(): string
{
return "{$this->name} sedang tidur";
}
public function getInfo(): string
{
return "{$this->name} berusia {$this->age} tahun";
}
}
class Dog extends Animal
{
private string $breed;
public function __construct(string $name, int $age, string $breed)
{
parent::__construct($name, $age);
$this->breed = $breed;
}
public function bark(): string
{
return "{$this->name} berkata: Guk! Guk!";
}
public function getInfo(): string
{
return parent::getInfo() . " dan merupakan {$this->breed}";
}
}
$dog = new Dog('Buddy', 3, 'Golden Retriever');
echo $dog->eat(); // Buddy sedang makan
echo $dog->bark(); // Buddy berkata: Guk! Guk!
echo $dog->getInfo(); // Buddy berusia 3 tahun dan merupakan Golden RetrieverPoin-poin kunci:
extends untuk mewarisi dari kelas indukparent:: untuk memanggil metode kelas indukprotected dapat diakses dalam kelas anakPolimorfisme berarti "banyak bentuk". Ini memungkinkan objek dari kelas berbeda diperlakukan melalui antarmuka yang sama. Ini sangat kuat untuk menulis kode yang fleksibel dan mudah dirawat.
interface PaymentProcessor
{
public function process(float $amount): bool;
public function refund(float $amount): bool;
}
class CreditCardProcessor implements PaymentProcessor
{
public function process(float $amount): bool
{
// Proses pembayaran kartu kredit
return true;
}
public function refund(float $amount): bool
{
// Pengembalian dana ke kartu kredit
return true;
}
}
class PayPalProcessor implements PaymentProcessor
{
public function process(float $amount): bool
{
// Proses pembayaran PayPal
return true;
}
public function refund(float $amount): bool
{
// Pengembalian dana via PayPal
return true;
}
}
class StripeProcessor implements PaymentProcessor
{
public function process(float $amount): bool
{
// Proses pembayaran Stripe
return true;
}
public function refund(float $amount): bool
{
// Pengembalian dana via Stripe
return true;
}
}
// Penggunaan polimorfik
function processOrder(PaymentProcessor $processor, float $amount): void
{
if ($processor->process($amount)) {
echo "Pembayaran berhasil diproses";
}
}
$creditCard = new CreditCardProcessor();
$paypal = new PayPalProcessor();
$stripe = new StripeProcessor();
processOrder($creditCard, 99.99);
processOrder($paypal, 49.99);
processOrder($stripe, 199.99);Fungsi processOrder tidak peduli pemroses pembayaran mana yang digunakan. Ini bekerja dengan kelas apa pun yang mengimplementasikan PaymentProcessor. Ini adalah kekuatan polimorfisme.
Kelas abstrak mendefinisikan kontrak yang harus diikuti subkelas:
abstract class DatabaseConnection
{
protected string $host;
protected string $database;
public function __construct(string $host, string $database)
{
$this->host = $host;
$this->database = $database;
}
abstract public function connect(): void;
abstract public function query(string $sql): array;
abstract public function close(): void;
public function getDatabase(): string
{
return $this->database;
}
}
class MySQLConnection extends DatabaseConnection
{
private $connection;
public function connect(): void
{
// Logika koneksi MySQL
$this->connection = mysqli_connect($this->host, 'user', 'pass', $this->database);
}
public function query(string $sql): array
{
// Jalankan query MySQL
return [];
}
public function close(): void
{
mysqli_close($this->connection);
}
}
class PostgreSQLConnection extends DatabaseConnection
{
private $connection;
public function connect(): void
{
// Logika koneksi PostgreSQL
$this->connection = pg_connect("host={$this->host} dbname={$this->database}");
}
public function query(string $sql): array
{
// Jalankan query PostgreSQL
return [];
}
public function close(): void
{
pg_close($this->connection);
}
}Abstraksi menyembunyikan kompleksitas dan menampilkan hanya fitur penting. Ini tentang membuat antarmuka yang disederhanakan untuk kode yang kompleks.
// Logika internal kompleks disembunyikan
class EmailService
{
private SMTPClient $smtpClient;
private TemplateEngine $templateEngine;
private Logger $logger;
public function __construct(
SMTPClient $smtpClient,
TemplateEngine $templateEngine,
Logger $logger
) {
$this->smtpClient = $smtpClient;
$this->templateEngine = $templateEngine;
$this->logger = $logger;
}
// Antarmuka publik yang sederhana
public function sendWelcomeEmail(string $email, string $name): bool
{
try {
$template = $this->templateEngine->render('welcome', ['name' => $name]);
$this->smtpClient->send($email, 'Selamat Datang!', $template);
$this->logger->info("Email selamat datang dikirim ke {$email}");
return true;
} catch (Exception $e) {
$this->logger->error("Gagal mengirim email selamat datang: {$e->getMessage()}");
return false;
}
}
public function sendPasswordResetEmail(string $email, string $resetToken): bool
{
try {
$template = $this->templateEngine->render('password-reset', ['token' => $resetToken]);
$this->smtpClient->send($email, 'Atur Ulang Kata Sandi Anda', $template);
$this->logger->info("Email pengaturan ulang kata sandi dikirim ke {$email}");
return true;
} catch (Exception $e) {
$this->logger->error("Gagal mengirim email pengaturan ulang kata sandi: {$e->getMessage()}");
return false;
}
}
}
// Kode klien tidak perlu tahu tentang SMTP, template, atau logging
$emailService = new EmailService($smtp, $template, $logger);
$emailService->sendWelcomeEmail('user@example.com', 'John');Pemanggil tidak perlu memahami protokol SMTP, rendering template, atau mekanisme logging. Mereka hanya memanggil metode sederhana.
Komposisi sering lebih baik daripada pewarisan. Alih-alih mewarisi perilaku, Anda menyusun objek yang memiliki perilaku yang Anda butuhkan.
// Alih-alih mewarisi dari Logger
class UserRepository
{
private Logger $logger;
private Database $database;
public function __construct(Logger $logger, Database $database)
{
$this->logger = $logger;
$this->database = $database;
}
public function findById(int $id): ?User
{
$this->logger->debug("Mengambil pengguna dengan ID: {$id}");
$result = $this->database->query("SELECT * FROM users WHERE id = ?", [$id]);
return $result ? new User($result) : null;
}
public function save(User $user): bool
{
try {
$this->database->query("INSERT INTO users ...", $user->toArray());
$this->logger->info("Pengguna disimpan: {$user->getId()}");
return true;
} catch (Exception $e) {
$this->logger->error("Gagal menyimpan pengguna: {$e->getMessage()}");
return false;
}
}
}Komposisi fleksibel: Anda dapat menukar implementasi logger atau database tanpa mengubah UserRepository.
Antarmuka mendefinisikan kontrak. Kelas yang mengimplementasikan antarmuka harus mengimplementasikan semua metodenya.
interface CacheStore
{
public function get(string $key): mixed;
public function put(string $key, mixed $value, int $ttl = 3600): void;
public function forget(string $key): void;
public function flush(): void;
}
class RedisCache implements CacheStore
{
private Redis $redis;
public function __construct(Redis $redis)
{
$this->redis = $redis;
}
public function get(string $key): mixed
{
return $this->redis->get($key);
}
public function put(string $key, mixed $value, int $ttl = 3600): void
{
$this->redis->setex($key, $ttl, serialize($value));
}
public function forget(string $key): void
{
$this->redis->del($key);
}
public function flush(): void
{
$this->redis->flushAll();
}
}
class FileCache implements CacheStore
{
private string $path;
public function __construct(string $path)
{
$this->path = $path;
}
public function get(string $key): mixed
{
$file = $this->path . '/' . md5($key);
if (!file_exists($file)) {
return null;
}
$data = unserialize(file_get_contents($file));
if ($data['expires'] < time()) {
unlink($file);
return null;
}
return $data['value'];
}
public function put(string $key, mixed $value, int $ttl = 3600): void
{
$file = $this->path . '/' . md5($key);
$data = ['value' => $value, 'expires' => time() + $ttl];
file_put_contents($file, serialize($data));
}
public function forget(string $key): void
{
$file = $this->path . '/' . md5($key);
if (file_exists($file)) {
unlink($file);
}
}
public function flush(): void
{
array_map('unlink', glob($this->path . '/*'));
}
}
// Kedua implementasi memenuhi antarmuka
function cacheUserData(CacheStore $cache, int $userId, array $data): void
{
$cache->put("user:{$userId}", $data, 3600);
}
$redisCache = new RedisCache($redis);
$fileCache = new FileCache('/tmp/cache');
cacheUserData($redisCache, 1, ['name' => 'John']);
cacheUserData($fileCache, 1, ['name' => 'John']);Trait memungkinkan Anda menggunakan kembali metode di seluruh kelas yang tidak terkait tanpa pewarisan.
trait Timestampable
{
private DateTime $createdAt;
private DateTime $updatedAt;
public function setCreatedAt(DateTime $date): void
{
$this->createdAt = $date;
}
public function getCreatedAt(): DateTime
{
return $this->createdAt;
}
public function setUpdatedAt(DateTime $date): void
{
$this->updatedAt = $date;
}
public function getUpdatedAt(): DateTime
{
return $this->updatedAt;
}
}
trait Loggable
{
private array $logs = [];
public function addLog(string $message): void
{
$this->logs[] = [
'message' => $message,
'timestamp' => date('Y-m-d H:i:s'),
];
}
public function getLogs(): array
{
return $this->logs;
}
}
class BlogPost
{
use Timestampable, Loggable;
private string $title;
private string $content;
public function __construct(string $title, string $content)
{
$this->title = $title;
$this->content = $content;
$this->setCreatedAt(new DateTime());
$this->addLog('Postingan dibuat');
}
public function publish(): void
{
$this->setUpdatedAt(new DateTime());
$this->addLog('Postingan dipublikasikan');
}
}
$post = new BlogPost('Postingan Pertama Saya', 'Konten di sini...');
$post->publish();
print_r($post->getLogs());Anggota statis milik kelas, bukan instansi. Gunakan untuk data bersama atau fungsi utilitas.
class Config
{
private static array $settings = [];
private static bool $initialized = false;
public static function initialize(array $config): void
{
if (self::$initialized) {
throw new Exception('Konfigurasi sudah diinisialisasi');
}
self::$settings = $config;
self::$initialized = true;
}
public static function get(string $key, mixed $default = null): mixed
{
return self::$settings[$key] ?? $default;
}
public static function set(string $key, mixed $value): void
{
self::$settings[$key] = $value;
}
}
Config::initialize(['app_name' => 'MyApp', 'debug' => true]);
echo Config::get('app_name'); // MyApp
Config::set('debug', false);Warning
Anggota statis dapat membuat pengujian sulit dan menciptakan ketergantungan tersembunyi. Gunakan dengan hemat. Injeksi ketergantungan biasanya lebih baik.
class Animal {}
class Mammal extends Animal {}
class Carnivore extends Mammal {}
class Feline extends Carnivore {}
class DomesticCat extends Feline {}
// Ini rapuh dan sulit dirawatinterface Eater {
public function eat(): void;
}
interface Sleeper {
public function sleep(): void;
}
class Cat implements Eater, Sleeper {
public function eat(): void {}
public function sleep(): void {}
}class User
{
public array $data = [];
}
$user = new User();
$user->data['password'] = 'plaintext'; // Berbahaya!class User
{
private string $password;
public function setPassword(string $password): void
{
$this->password = password_hash($password, PASSWORD_BCRYPT);
}
}function processUser($user)
{
return $user->getName();
}function processUser(User $user): string
{
return $user->getName();
}class OrderService
{
public function __construct(
private PaymentProcessor $paymentProcessor,
private EmailService $emailService,
private Logger $logger,
) {}
public function placeOrder(Order $order): bool
{
if (!$this->paymentProcessor->process($order->getTotal())) {
$this->logger->error('Pembayaran gagal untuk pesanan ' . $order->getId());
return false;
}
$this->emailService->sendOrderConfirmation($order);
$this->logger->info('Pesanan ditempatkan: ' . $order->getId());
return true;
}
}class Product
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly float $price,
public readonly bool $available = true,
) {}
public function getDiscountedPrice(float $discountPercent): float
{
return $this->price * (1 - $discountPercent / 100);
}
public function isAffordable(float $budget): bool
{
return $this->price <= $budget;
}
}class NotificationService
{
public function send(
string $recipient,
string $message,
string $channel = 'email',
bool $urgent = false,
): void {
$priority = match ($channel) {
'sms' => $urgent ? 'high' : 'normal',
'email' => 'normal',
'push' => 'high',
default => throw new InvalidArgumentException("Saluran tidak dikenal: {$channel}"),
};
}
}
// Named arguments membuat panggilan jelas
$service->send(
recipient: 'user@example.com',
message: 'Pesanan Anda siap',
channel: 'email',
urgent: false,
);OOP bukan selalu jawabannya. Pertimbangkan alternatif:
// ✅ Sederhana dan jelas
function calculateTax(float $amount, float $rate): float
{
return $amount * ($rate / 100);
}
// ❌ Over-engineered
class TaxCalculator
{
private float $rate;
public function __construct(float $rate) { $this->rate = $rate; }
public function calculate(float $amount): float { return $amount * ($this->rate / 100); }
}OOP di PHP menyediakan alat yang kuat untuk membangun aplikasi yang mudah dirawat dan dapat diskalakan. Kuasai konsep-konsep ini:
Mulai dengan prinsip SOLID, gunakan injeksi ketergantungan, dan manfaatkan fitur PHP 8+ seperti typed properties dan constructor promotion. Kode Anda akan lebih bersih, lebih mudah diuji, dan lebih mudah dirawat.
Kode OOP terbaik adalah kode yang menyelesaikan masalah dengan jelas tanpa kompleksitas yang tidak perlu. Tulis untuk pengembang berikutnya yang membaca kode Anda—itu mungkin Anda dalam enam bulan.