Memakai Traits Di PHP 5.4


Salah satu fitur baru di PHP 5.4 adalah traits. Pada bahasa Groovy, traits masih sedang dalam proses pengembangan dan sedang didiskusikan. Apa itu traits? Anggap saja traits adalah copy-paste kode program yang dilakukan melalui bahasa pemograman. Kode program di dalam sebuah traits akan di-paste ke class lain yang memakainya.

Bukankah salah satu cara reuse kode program di OPP adalah dengan menggunakan inheritance? Saya sudah menunjukkannya di artikel Memakai Reflection API Di PHP. Pada artikel tersebut, method pada class DomainObject dapat dipakai di seluruh class turunannya.

Lalu, untuk apa ada traits? Kapan traits dipakai? Jawaban singkatnya adalah pada saat sebuah class perlu men-implementasi-kan lebih dari satu superclass. PHP, seperti Java, tidak mendukung multiple inheritance. Traits adalah alternatif untuk menerapkan multiple inheritance. Btw, saya sangat jarang sekali menemukan harus memakai multiple inheritance. Biasanya kebutuhan untuk memakai traits timbul pada saat saya membuat library/API dimana saya harus mengikuti API lain yang wajib men-extends class mereka, tetapi saya juga ingin menambahkan fitur baru yang reusable. Karena saya hanya boleh men-extends sebuah class, maka fitur reusable saya terpaksa harus di-implementasi-kan dalam bentuk traits.

Saat saya merancang domain class, sangat jarang sekali timbul kebutuhan untuk memakai traits. Oleh sebab itu, pada artikel ini, saya membuat kasus fiktif dimana saya mencoba menyelesaikannya dengan menggunakan traits. Kasus fiktif tersebut adalah sebuah kasus pada aplikasi penjualan, dimana order dapat terdiri atas produk atau paket atau keduanya. Sebuah paket adalah kumpulan dari beberapa produk yang dijual secara bersamaan. Selain itu, juga ada promosi. Sebuah promosi bisa berlaku untuk produk tunggal, beberapa produk, sebuah paket, beberapa paket, atau kombinasi produk dan paket. UML class diagram berikut memperlihatkan struktur rancangan awal class secara sederhana:

Sebuah rancangan awal

Sebuah rancangan awal

Terlihat bahwa seluruh class yang ada diturunkan dari sebuah abstract class bernama DomainObject. Bila diperhatikan lebih lanjut, pada class Promosi dan class ProdukPaket terdapat beberapa method dan operasi yang sama dengan fungsi yang sama. Duplikasi adalah code smell yang dapat menimbulkan masalah di kemudian hari. Oleh sebab itu, saya bisa me-refactor design tersebut menjadi seperti berikut ini:

Rancangan mutliple inheritance setelah refactor

Rancangan mutliple inheritance setelah refactor

Pada gambar di atas, saya membuat sebuah class baru, KomposisiProduk untuk mewakili sesuatu yang merupakan gabungan dari beberapa produk, misalnya ProdukPaket dan Promosi. Karena KomposisiProduk bukanlah sebuah entitas yang perlu disimpan ke database, maka saya tidak menurunkannya dari DomainObject.

Sampai disini, saya mengalami masalah yang berkaitan dengan bahasa PHP (dan juga Java). Sebuah class hanya boleh diturunkan (baca:inheritance) dari satu class lain. Pada rancangan di atas, class ProdukPaket diturunkan dari class Produk dan class KomposisiProduk. Ini adalah multiple inheritance dan tidak didukung oleh PHP (serta Java)! Saya perlu menurunkan ProdukPaket dari Produk karena class tersebut harus dianggap sebagai Produk yang dapat diperjualbelikan (misalnya oleh Order atau Promosi). Tapi tentu saja ProdukPaket adalah KomposisiProduk karena ia bisa terdiri atas lebih dari satu Produk. Jadi, ProdukPaket memiliki dua sifat tersebut. Apa yang harus saya lakukan untuk mengimplementasikan class ProdukPaket di PHP?

Salah satu solusi yang dapat saya tempuh adalah dengan mendefinisikan KomposisiProduk sebagai sebuah trait. Sebagai contoh, saya mendefinisikannya seperti berikut ini:

<?php

namespace domain;

trait KomposisiProduk {

    /**
     * Sebuah `SplObjectStorage` untuk menampung daftar `Produk`.
     *  
     * @var \SplObjectStorage
     */
    public $listProduk;

    /**
     * Menambah `Produk` baru untuk komposisi ini.
     * 
     * @param Produk $produk
     */
    public function tambahProduk($produk) {
        if ($this->listProduk === NULL) {
            $this->listProduk = new \SplObjectStorage();
        }               
        $this->listProduk->attach($produk);
    }

    /**
     * Menghapus `Produk` dari komposisi ini.
     * 
     * @param Produk $produk
     */
    public function hapusProduk($produk) {
        $this->listProduk->detach($produk);       
    }

}

?>

Pada kode program di atas, saya memakai SplObjectStorage untuk menampung object. Proses menambah dan menghapus object menjadi lebih mudah bila memakai SplObjectStorage ketimbang memakai array biasa. Sebuah trait tidak dapat dipakai secara langsung (dibuat instance-nya). Ia dipakai dengan disertakan pada class lain. Sebagai contoh, class ProdukPaket akan memakai trait di atas seperti yang terlihat pada kode program berikut ini:

<?php

namespace domain;

class ProdukPaket extends Produk {

    use KomposisiProduk;

    public function __construct($kode = NULL, $nama = NULL) {
        if ($kode) $this->kode= $kode;
        if ($nama) $this->nama = $nama;      
    }

    public function getHarga() {
        $harga = 0;
        foreach ($this->listProduk as $produk) {
            $harga += $produk->getHarga();           
        }
        return $harga;      
    }
}

?>

Pada kode program di atas, saya memakai trait dengan menggunakan keyword use. Jangan samakan ini dengan memakai namespace! Walaupun sama-sama memakai keyword use, mereka memiliki fungsi yang sangat berbeda! use pada trait memiliki arti bahwa seluruh kode program yang ada di KomposisiProduk akan di-copy paste ke class yang memakainya sementara use pada namespace berfungsi untuk men-import class yang dibutuhkan.

Sebuah trait dapat dipakai di beberapa class yang berbeda. Demikian juga, sebuah class dapat memakai lebih dari satu trait. Sebagai contoh, class Promosi juga akan memakai trait KomposisiProduk, seperti yang terlihat pada kode program berikut ini:

<?php

namespace domain;

class Promosi extends DomainObject {

    use KomposisiProduk;

    public $kode;

    public $nama;

    function __construct($kode = NULL, $nama = NULL) {
        if ($kode) $this->kode = $kode;
        if ($nama) $this->nama = $nama;
    }

}

?>

Untuk menguji domain class yang telah dibuat, saya akan membuat sebuah halaman PHP baru yang isinya seperti berikut ini:

<html>
<body>
<pre>
<?php

    spl_autoload_extensions('.php');
    spl_autoload_register();

    use domain\Produk;
    use domain\Promosi;
    use domain\ProdukPaket; 

    $produk1 = new Produk("SKU01112355", "ASRock Motherboard Socket LGA1155", 1534500); 
    $produk2 = new Produk("SKU01212438", "Intel Core i3-3220", 1555400);
    $produk3 = new Produk("SKU00310470", "Corsair Memory PC 4GB DDR3 PC-12800", 689700);

    $produk4 = new Produk("SKU01212254", "ASUS Motherboard Socket AM3/AM3+", 1190200);
    $produk5 = new Produk("SKU00306298", "AMD Phenom II X2 555 Black", 1068100);

    $produk6 = new Produk("SKU01112671", "Logitech Wireless Touch Keyboard K400R - Black", 541200);

    $paketIntel = new ProdukPaket("PI01", "Paket Intel 01");
    $paketIntel->tambahProduk($produk1);
    $paketIntel->tambahProduk($produk2);
    $paketIntel->tambahProduk($produk3);

    print "Paket Intel terdiri atas:\n\n";  
    foreach($paketIntel->listProduk as $p) {     
        printf("%35s %15d\n", $p->nama, $p->harga);
    }
    print "\nHarga Total Adalah: {$paketIntel->getHarga()}\n\n";

    $paketAmd = new ProdukPaket("PA01", "Paket AMD 01");
    $paketAmd->tambahProduk($produk4);
    $paketAmd->tambahProduk($produk5);
    $paketAmd->tambahProduk($produk3);

    print "Paket AMD terdiri atas:\n\n";
    foreach($paketAmd->listProduk as $p) {
        printf("%35s %15d\n", $p->nama, $p->harga);
    }
    print "\nHarga Total Adalah: {$paketAmd->getHarga()}\n\n";

    $promosiNatal = new Promosi("PRM011213", "Promosi Natal 2013");
    $promosiNatal->tambahProduk($paketAmd);
    $promosiNatal->tambahProduk($produk6);

    print "\n\nPromosi {$promosiNatal->nama} terdiri atas:\n\n";
    foreach ($promosiNatal->listProduk as $p) {
        printf("%50s %15d\n", $p->nama, $p->getHarga());
    }


?>
</pre>
</body>
</html>

Perhatikan bahwa object paketIntel, paketAmd dan promosiNatal akan memiliki method tambahProduk dan property listProduk secara otomatis berkat traits. Selain itu, paketIntel dan paketAmd tetap merupakan sebuah Produk sehingga mereka bisa ditambahkan ke Promosi atau Order sama seperti Produk lainnya. Bila dijalankan, halaman PHP di atas akan terlihat seperti berikut ini:

Paket Intel terdiri atas:

  ASRock Motherboard Socket LGA1155         1534500
                 Intel Core i3-3220         1555400
Corsair Memory PC 4GB DDR3 PC-12800          689700

Harga Total Adalah: 3779600

Paket AMD terdiri atas:

   ASUS Motherboard Socket AM3/AM3+         1190200
         AMD Phenom II X2 555 Black         1068100
Corsair Memory PC 4GB DDR3 PC-12800          689700

Harga Total Adalah: 2948000



Promosi Promosi Natal 2013 terdiri atas:

                                      Paket AMD 01         2948000
    Logitech Wireless Touch Keyboard K400R - Black          541200

Btw, penggunaan traits yang berlebihan dapat menimbulkan masalah di kemudian hari. Terkadang pandangan ini dapat diperdebatkan karena pada dasarnya seorang programmer yang memakai bahasa dinamis harusnya sudah siap menanggung resiko tinggi dari awal. Walaupun demikian, ada baiknya untuk mencoba mencari solusi memakai inheritance atau dependency injection terlebih dahulu sebelum akhirnya harus memakai traits.

Perihal Solid Snake
I'm nothing...

One Response to Memakai Traits Di PHP 5.4

  1. andrias siwabessy mengatakan:

    Terima kasih untuk penjelasannya.
    Sangat mudah dipahami & membantu saya memahami trait pada PHP..🙂
    GBU

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: