Memahami Penggunaan PHP5 Abstract & Interface di Laravel — Part 1


Memahami Abstract dan Interfaces sangat penting untuk dapat mendalami framework Laravel. Jika pernah mempelajari mempelajari OOP di PHP, pasti telah memahami Class dan Inheritance. Jika Class dan Inheritance adalah nasi dan sayur asem, maka Abstract dan Interfaces adalah ikan asin dan sambelnya. Nah. :D

Abstract

Oke. Kita mulai dari Abstract. Apa itu Abstract?

Abstrak adalah tipe yang tidak dapat dipakai secara langsung. (Wikipedia)

Maksudnya, Abstract itu adalah semacam class di PHP tapi tidak bisa langsung dibuat objectnya. Misalnya, sebuah tombol. Kita semua pasti tahu, bahwa tombol apapun pasti bisa ditekan. Hanya saja, tiap tindakan yang terjadi ketika kita menekan tombol akan berbeda, tergantung jenis tombolnya. Perhatikan contoh ini:

file: ~/Code/abstract-wal-interfaces/Tombol.php

<?php
abstract class Tombol {
abstract public function tekan();
}

file: ~/Code/abstract-wal-interfaces/contoh-abstract-gagal.php

<?php
include 'Tombol.php';
$tombol = new Tombol();
$tombol->tekan();

Terlihat disini, kita langsung mencoba membuat object (instantiate) dari abstract class Tombol. Maka, akan ada error seperti ini:

Class Abstract tidak bisa langsung dibuat object

Ini menunjukkan bahwa abstract tidak bisa langsung di-instantiate menjadi object. Untuk membuat object, kita perlu membuat class baru yang merupakan turunan dari abstract class Tombol ini. Perhatikan syntax ini:

file: ~/Code/abstract-wal-interfaces/TombolLogin.php

<?php
include “Tombol.php”;
class TombolLogin extends Tombol {
}

file: ~/Code/abstract-wal-interfaces/contoh-abstract-sukses.php

<?php
include "TombolLogin.php";
$tombol = new TombolLogin();
$tombol->tekan();

Disini kita membuat class baru bernama TombolLogin yang meng-extend abstract class Tombol. Kalau kita jalankan :

Gagal meng-extends interface, karena ada method yang belum diimplementasikan

Uuuppss… Dia error lagi…

Ini terjadi karena, kita belum mengimplementasikan method abstract yaitu method tekan() yang ada di abstract class Tombol. Mari kita perbaiki:

file: ~/Code/abstract-wal-interfaces/TombolLogin.php

<?php
include "Tombol.php";
class TombolLogin extends Tombol {
public function tekan() {
echo “Berhasil login!\n”;
}
}

Kalau kita jalankan lagi:

Berhasil menggunakan abstract class Tombol

Tuhh.. dia berhasil kan? ☺

Ini penting. Jadi, abstract itu sangat berguna kalau kita ingin memastikan bahwa suatu method selalu tersedia pada class, apapun implementasinya. Dalam contoh ini, kita selalu bisa memanggil method tekan() apapun jenis tombolnya. Biar lebih paham, kita tambah lagi contohnya. Kali ini, ceritanya kita mau bikin tombol untuk meluncurkan bom nuklir. Perlu dicatat, ini hanya contoh, tidak ada yang bom yang diluncurkan dalam pembuatan contoh ini. Perhatikan syntax ini:

file: ~/Code/abstract-wal-interfaces/TombolNuklir.php

<?php
include “Tombol.php”;
class TombolNuklir extends Tombol {
public function tekan() {
echo “Bom nuklir telah diluncurkan!\n”;
sleep(3);
echo “Boooooommmmm!!!\n”;
}
}

Untuk menjalankan tombol ini, kita tetap menggunakan method yang sama, yaitu tekan():

file: ~/Code/abstract-wal-interfaces/contoh-abstract-sukses-2.php

<?php
include "TombolNuklir.php";
$tombol = new TombolNuklir();
$tombol->tekan();

Kalau dijalankan..

Berhasil membuat Tombol Nuklir

Boooooommmmm!!!

Oke, saya rasa sudah cukup penjelasannya. Pertanyaanya, kapan biasanya teknik abstract class ini digunakan?

Contohnya banyak, salah satunya saya contohkan dengan dependency injection. Teknik ini memudahkan kita untuk memasukkan (inject) class yang kita butuhkan pada class yang sedang digunakan.

Contoh dependency injection, misalnya seorang Pembeli harus punya kartu BNI untuk melakukan pembayaran. Bisa kita implementasikan dengan meng-inject class BNI ke class Pembeli. Seperti ini:

file: ~/Code/abstract-wal-interfaces/BNI.php

<?php
class BNI {
  private $saldo;
  public function __construct($pin) {
// ceritanya cek PIN ke database
if ($pin == ‘12345’) {
echo "Berhasil mengaktifkan Kartu BNI!\n";
} else {
$pesan = "PIN yang Anda masukkan salah :(";
throw new Exception($pesan);
}
}
  private function catatTransaksi($jenis, $jumlah) {
echo "Mencatat transaksi $jenis sejumlah $jumlah ke Buku Tabungan.\n";
}
  public function kredit($jumlah) {
$this->catatTransaksi(‘transfer keluar’, $jumlah);
$this->saldo -= $jumlah;
}
  public function deposit($jumlah) {
$this->catatTransaksi(‘deposit dana’, $jumlah);
$this->saldo += $jumlah;
}
  public function cekSaldo() {
return $this->saldo;
}
}

Class BNI ini mempunyai:

  • attribute $saldo yang berfungsi mencatat saldo terakhir.
  • method __construct() yang berfungsi membangun object BNI, di method ini kita mengharuskan input PIN. Dalam prakteknya, tentu saja PIN ini disimpan di database, tapi disini kita sederhanakan dengan menyimpannya langsung di method ini.
  • method kredit() yang berfungsi untuk mengurangi jumlah saldo.
  • method deposit() yang berfungsi untuk menambah jumlah saldo.
  • method cekSaldo() yang berfungsi mengecek jumlah saldo terkini.

Mari kita buat class Pembeli, class ini akan membutuhkan class BNI :

file: ~/Code/abstract-wal-interfaces/Pembeli-DI.php

<?php
include "BNI.php";
class Pembeli {
private $nama;
private $bni;
 public function __construct($nama = "Seseorang", BNI $bni) {
$this->bni = $bni;
$this->nama = $nama;
}
 public function beli($nama = "Barang", $harga = 0) {
$this->bni->kredit($harga);
echo "Berhasil melakukan pembelian $nama seharga Rp$harga.\n";
echo "Terima kasih $this->nama :)\n";
}
}

Class Pembeli ini mempunyai :

  • atribut $nama untuk menyimpan nama pembeli
  • atribut $bni untuk menyimpan object kartu BNI
  • method beli() untuk melakukan pembelian barang
  • method __construct() untuk membangun object Pembeli

Yang paling penting untuk diperhatikan disini adalah method __construct(). Di method ini kita menginject class BNI sebagai parameternya. Kalau didemokan syntaxnya seperti ini:

file: ~/Code/abstract-wal-interfaces/beli-pakai-bni.php

<?php
require_once "Pembeli-DI.php";
// Melakukan pembelian dengan BNI
try {
$bniKu = new BNI('12345');
$bniKu->deposit(20000000);
$rudy = new Pembeli("Rudy", $bniKu);
$rudy->beli("CD Smash — Step Forward", 80000);
echo "Saldo terakhir Rp".$bniKu->cekSaldo()."\n";
} catch (Exception $e) {
echo $e->getMessage()."\n";
}

Terlihat disini, sebelum membuat object Pembeli, kita membuat object BNI dulu. Kemudian meng-inject object BNI itu ketika membuat object Pembeli. Jika dijalankan, hasilnya seperti berikut :

Berhasil meng-*inject* BNI ke Pembeli

Masalah dari dependency injection ini adalah bagaimana bila kita akan menggunakan metode pembayaran lain? Misalnya, Paypal. Tentunya, cara mengakses paypal ini pun akan berbeda dengan BNI, karena Paypal harus login dengan email dan password. Begitupun cara paypal melakukan kredit dan deposit, karena paypal perlu mengirim email setiap kali terjadi transaksi.

Kalau gitu langsung di extends dari class BNI saja gimana mas?

Memang, sekiranya implementasinya akan sama, kita cukup meng-extends class Paypal dari BNI. Namun, karena implementasi dari method kredit() dan deposit() ini bisa berbeda, maka fitur pembayaran ini cocok untuk di-abstraksi. Dengan abstraksi pula, akan lebih memudahkan jika akan ada implementasi jenis pembayaran yang baru, misalnya BitCoin.

Kita bisa membuat abstraksi dengan membuat class abstract yang berisi method apa saja yang harus ada di Class tersebut yang akan digunakan di class Pembeli. Tahapannya dimulai dari class Pembeli, ubah menjadi :

file: ~/Code/abstract-wal-interfaces/Pembeli.php

<?php
class Pembeli {
private $nama;
private $payment;
  public function __construct($nama = "Seseorang", PaymentMethod $payment) {
$this->nama = $nama;
$this->payment = $payment;
}
  public function beli($nama = "Barang", $harga = 0) {
if ($this->payment->cekSaldo() < $harga) {
echo "Uang tidak cukup\n";
} else {
$this->payment->kredit($harga);
echo "Terima kasih $this->nama :)\n";
echo "Berhasil melakukan pembelian $nama seharga Rp".number_format($harga).".\n";
}
}
}

Perubahan terbesar dari class Pembeli adalah kita mengubah atribut $bni menjadi $payment dan mengubah mengabstraksi class BNI menjadi PaymentMethod.

Terlihat disini, class PaymentMethod perlu menambahkan beberapa method:

  • cekSaldo() untuk mengecek saldo terakhir
  • kredit() untuk mengambil sejumlah uang
  • deposit() untuk mengisi sejumlah uang

Untuk memudahkan pengecekan, kita akan menambah method cekNamaPembayaran() yang berfungsi menampilkan nama Class yang digunakan untuk melakukan pembayaran. Mari kita buat abstract class PaymentMethod :

file: ~/Code/abstract-wal-interfaces/PaymentMethod.php

<?php
abstract class PaymentMethod {
public function cekNamaPembayaran() {
return "Anda melakukan pembayaran dengan ".get_class($this)."\n";
}
abstract public function kredit($jumlah);
abstract public function deposit($jumlah);
abstract public function cekSaldo();
}

Selanjutnya, ubah class BNI agar mengekstends PaymentMethod. Untuk memudahkan contoh, kita ubah nama classnya menjadi DebitBNI:

file: ~/Code/abstract-wal-interfaces/DebitBNI.php

<?php
require_once "PaymentMethod.php";
class DebitBNI extends PaymentMethod {
private $saldo;
  public function __construct($pin) {
// ceritanya cek PIN ke database
if ($pin == '12345') {
echo "Berhasil mengaktifkan Kartu Debit!\n";
} else {
$pesan = "PIN yang Anda masukkan salah :(";
throw new Exception($pesan);
}
}
  private function catatTransaksi($jenis, $jumlah) {
echo "Mencatat transaksi $jenis sejumlah $jumlah ke Buku Tabungan.\n";
}
  public function kredit($jumlah) {
$this->catatTransaksi('transfer keluar', $jumlah);
$this->saldo -= $jumlah;
}
  public function deposit($jumlah) {
$this->catatTransaksi('deposit dana', $jumlah);
$this->saldo += $jumlah;
}
  public function cekSaldo() {
return $this->saldo;
}
}

Mari kita buat demo untuk metode pembayaran ini:

file: ~/Code/abstract-wal-interfaces/beli-pakai-debitbni.php

<?php
require_once "DebitBNI.php";
require_once "Pembeli.php";
// Melakukan pembelian dengan DebitBNI
try {
$paymentMethod = new DebitBNI("12345");
$paymentMethod->deposit(20000000);
$rahmat = new Pembeli("Morgan", $paymentMethod);
$rahmat->beli("Sepatu Dance", 250000);
echo "Saldo terakhir Rp".number_format($paymentMethod->cekSaldo())."\n";
echo $paymentMethod->cekNamaPembayaran();
} catch (Exception $e) {
echo $e->getMessage()."\n";
}

Di baris terakhir output terlihat kita menggunakan implementasi PaymentMethod dengan class DebitBNI.

Untuk implementasi Paypal, kita buat seperti ini:

file: ~/Code/abstract-wal-interfaces/Paypal.php

<?php
require_once 'PaymentMethod.php';
class Paypal extends PaymentMethod {
private $balance;
  public function __construct($email, $password) {
// Ceritanya ini akses ke database
if ($email == "morgan@gmail.com" & $password == "12345") {
$this->email = $email;
echo "Berhasil login ke Paypal!\n";
} else {
$pesan = "User ada user dengan username/password tersebut :(";
throw new Exception($pesan);
}
}
  private function kirimNotifikasi($pesan = "Informasi penting") {
echo "Mengirim email notifikasi $pesan ke $this->email \n";
}
  public function kredit($jumlah) {
$this->kirimNotifikasi('pengeluaran dana');
$this->balance -= $jumlah;
}
  public function deposit($jumlah) { 
$this->kirimNotifikasi('penerimaan dana');
$this->balance += $jumlah;
}
  public function cekSaldo() {
return $this->balance;
}
}

Terlihat disini, class Paypal ini mengimplementasikan semua method dari class abstract PaymentMethod, ini diharuskan. Karena, sebagaimana saya jelaskan di pembahasan sebelumnya, class yang meng-ekstends abstract class harus mengimplementasikan semua abstract methodnya. Jika tidak, aplikasi akan error.

Perbedaan lain di class Paypal adalah :

  • Untuk membuat object harus menggunakan email dan password yang kita hardcode di method __construct(). Tentunya, di kenyataannya kita akan mengecek ini ke database.
  • Atribut $balance digunakan untuk menyimpan dana.
  • Setiap kali ada transaksi uang masuk atau keluar, memanggil method kirimNotifikasi() yang disimulasikan akan mengirim email.

Demo dari metode pembayaran ini :

file: ~/Code/abstract-wal-interfaces/beli-pakai-paypal.php

<?php
require_once "Paypal.php";
require_once "Pembeli.php";
// Melakukan pembelian dengan paypal
try {
$paymentMethod = new Paypal("morgan@gmail.com", "12345");
$paymentMethod->deposit(12000000);
$pembeli = new Pembeli("Morgan", $paymentMethod);
$pembeli->beli("Poster Smash Full Color", 100000);
echo "Saldo terakhir Rp".number_format($paymentMethod->cekSaldo())."\n";
echo $paymentMethod->cekNamaPembayaran();
} catch (Exception $e) {
echo $e->getMessage()."\n";
}

Outputnya akan menjadi :

Membuat Paypal dengan abstract PaymentMethod

Jika diperhatikan, method pada class $paymentMethod yang kita gunakan disini, sama dengan method yang kita pakai di demo dengan pembayaran DebitBNI. Inilah kekuatan dari abstract class. Kita bisa melakukan standarisasi nama method, apapun bentuk implementasinya.

Nah.. udah ah. Segini aja dulu. Silahkan istirahat, ambil snack dan minumannya ya..


P.S: Artikel ini merupakan salah satu materi di buku saya Menyelami Framework Laravel. Download di http://leanpub.com/bukularavel