Melakukan Profiling Dengan Zend Studio

Profiling adalah sebuah aktifitas untuk mengukur kode program, seperti waktu eksekusi sebuah function atau jumlah pemanggilan sebuah function. Dengan melakukan profiling, seorang programmer akan mengetahui bagian mana dari kode programnya yang lambat atau bagian mana yang lebih sering dipanggil. Zend Studio dilengkapi dengan profiler untuk situs PHP. Syarat untuk menggunakan profiler tersebut adalah server yang men-host aplikasi PHP harus memiliki extension Zend Debugger. Karena saya memakai Zend Server CE (versi gratis), maka Zend Debugger sudah ter-install dan aktif.

Untuk melakukan profiling dari Zend Studio, saya dapat men-klik kanan sebuah halaman PHP, lalu memilih Profile As, PHP Web Application. Sebagai alternatif lainnya, saya dapat memilih menu Run, Profile URL dan memasukkan URL yang merujuk ke halaman PHP yang akan diperiksa. Selain itu, bila saya men-install Zend Studio Toolbar di browser, maka saya dapat langsung men-klik tombol Profile saat mengunjungi sebuah halaman PHP seperti yang terlihat pada gambar berikut ini:

Melakukan profiling dari browser

Melakukan profiling dari browser

Setelah itu, Zend Studio akan menampilkan konfirmasi untuk beralih ke PHH Profile perspective. Bagi yang belum terbiasa dengan Eclipse, sebuah perspective adalah kumpulan beberapa window dengan lokasi tertentu yang biasanya dipakai untuk keperluan tertentu. Perspective yang biasa dipakai untuk mengembangkan kode program adalah PHP perspective. Untuk melakukan debugging, terdapat Debug perspective. Pengguna dapat berganti perspective dengan men-klik icon perspective yang diinginkan di sisi kanan atas Eclipse, atau memilih menu Window, Open Perspective. Bagi yang terbiasa dengan produk Adobe, perspective di Eclipse mirip seperti workspace di Adobe. Sebagai contoh, pada Adobe Photoshop CS5, saya dapat memilih workspace seperti Essentials, Design, Painting, 3D dan sebagainya. Jadi, jangan lupa bila ingin kembali coding, ganti perspective dari PHP Profile persepective menjadi PHP perspective.

Pada Profile Information, saya dapat menemukan grafis pie yang menunjukkan waktu eksekusi kode program, seperti yang terlihat pada gambar berikut ini:

Diagram pie untuk waktu ekseksusi

Diagram pie untuk waktu ekseksusi

Kode program yang saya profile adalah sebuah halaman PHP sederhana yang mengambil data dari database dan menampilkannya pada view dengan menggunakan Twig. Grafis di atas memperlihatkan bahwa Autoloader.php (untuk loading class yang dibutuhkan Twig) dan Database.php (melakukan query database) membutuhkan waktu paling lama.

Saya dapat menemukan informasi lebih detail di Execution Statistics seperti yang terlihat pada gambar berikut ini:

Tabel untuk execution statistics

Tabel untuk execution statistics

Sebagai contoh, gambar di atas memperlihatkan bahwa function autoload() di Autoloader.php dikerjakan sebanyak 70 kali. Saya dapat melihat informasi lebih detail lagi dengan men-klik kanan function tersebut dan memilih Open function invocation statistics. Zend Studio profiler akan menampilkan informasi seperti yang terlihat pada gambar berikut ini:

Tabel untuk invocation statistics

Tabel untuk invocation statistics

Bagian Selected function is invoked by: pada tab Function Invocation Statistics akan memperlihatkan siapa saja yang mengerjakan function autoload(). Sebagai informasi, ini adalah fungsi yang akan dikerjakan setiap kali terdapat penggunaan sebuah class baru yang belum dikenali. autoload() bertugas men-include file yang mewakili deklarasi class tersebut. Saya dapat memperoleh informasi lebih lanjut class apa saja yang dibuat dengan memeriksa bagian Selected function invokes: seperti yang terlihat pada gambar berikut ini:

Tabel untuk invoke statistics

Tabel untuk invoke statistics

Pada tab Execution Flow, saya dapat melihat statistik function dimana setiap function diurutkan berdasarkan urutan eksekusi, seperti yang terlihat pada gambar berikut ini:

Tabel untuk execution flow

Tabel untuk execution flow

Apa itu Zend Optimizer+ Di Zend Server?

Sebuah fakta yang tidak dapat dipungkiri adalah PHP kalah dari Java dalam hal kinerja bila dilihat dari sisi arsitektur kedua teknologi tersebut. Tapi beberapa mahasiswa yang baru belajar tampaknya sulit menerima fakta ini. Beberapa dari mereka yang kritis akan bertanya seperti “Startup server Java lebih lama dibanding PHP, kenapa Java bisa lebih kencang?” atau “Bukankah Java butuh lebih banyak memori dibanding PHP?” Apa yang mereka ungkapkan memang benar, tapi saat request dari pengguna web diproses, server berbasis Java memiliki peluang besar untuk menghasilkan respon lebih cepat. Mengapa demikian?

Alasan pertama, kode program Java di-compile terlebih dahulu menjadi bytecode (file class) oleh programmer-nya. Apa yang di-deploy ke server Java adalah hasil kompilasi (file class) dalam bentuk file jar, bukan kode program Java! Dengan demikian, server dapat langsung menjalankannya. Lalu bagaimana dengan PHP? Tidak ada compiler PHP yang perlu dipanggil oleh programmer PHP!! Programmer PHP langsung men-deploy aplikasi web dalam bentuk kode program PHP (file PHP). Oleh sebab itu, saat sebuah halaman PHP dibuka, maka kode program di file PHP tersebut perlu di-parse dan di-compile ke dalam bytecode sebelum bisa di-eksekusi oleh CPU.

Alasan kedua, server Java adalah sebuah container dimana seluruh objek yang masih berguna tetap ‘hidup’ dan dipakai lagi oleh objek lainnya. Berbeda dengan Java, file PHP akan kembali dikerjakan pada saat request diberikan dan setelah selesai dikerjakan, semuanya ‘lenyap’. Sebagai contoh, perhatikan kode program berikut ini:

<?php
class Counter {             
    public static $counter;     
}

print "Counter: " . ++Counter::$counter;    
?>

Keyword static menunjukkan bahwa property counter dapat diakses kapan saja selama class Counter ada. Bila saya menjalankan kode program PHP di atas berkali-kali, nilai $counter akan selalu 1. Hal ini karena setiap kali saya membuka halaman PHP tersebut, class Counter baru akan tercipta dan setelah mencapai akhir eksekusi, class Counter tersebut akan musnah. Satu-satunya cara untuk mempertahankan nilai adalah memakai session, teknologi caching seperti Memcache, atau menyimpan nilai ke database. Perilaku ini berbeda dengan Java: class Counter akan selalu ada di memori dan nilai counter tetap dapat diakses setiap saat (termasuk dari request berbeda). Sebagai konsekuensinya, server Java membutuhkan lebih banyak memori guna menampung object yang masih ‘hidup’.

Alasan ketiga, JVM yang dipakai untuk server Java biasanya memiliki fasilitas Just In Time (JIT) code generation. Saat bytecode Java di-eksekusi pertama kali, JIT akan melakukan analisa dan bila perlu akan mengubah bytecode tersebut menjadi instruksi mesin (native) yang dapat langsung dikerjakan oleh CPU. Hal ini menyebabkan lambatnya eksekusi bytecode untuk pertama kali, tetapi eksekusi selanjutnya menjadi jauh lebih cepat (setara dengan bahasa native di platform tersebut). PHP tidak memiliki fasilitas seperti ini. Untuk itu, Facebook mengembangkan HipHop Virtual Machine (HHVM) yang merupakan sebuah execution engine untuk PHP. HHVM adalah proyek open source yang dapat dilihat di https://github.com/facebook/hhvm dan telah dipakai oleh situs facebook.com sejak awal tahun 2013. HHVM memiliki JIT code generation. Facebook telah menciptakan ‘PHP virtual machine’ yang dapat dibandingkan dengan Java Virtual Machine (JVM)! Tapi ini adalah contoh kasus tidak lazim. Kebanyakan fans awam menggunakan PHP ‘biasa’ (Zend Engine) seperti pada konfigurasi XAMPP ūüėČ

Jadi, PHP akan selalu men-parse dan men-compile file PHP setiap kali ia dipanggil. Bila file PHP tersebut men-include file lainnya, maka file lain tersebut juga perlu di-parse dan di-compile. Bila saya memakai framework (seperti Zend Framework dan Symphony), kemungkinan akan ada banyak file yang harus di-include. Ini belum lagi ditambah fakta bahwa proses parsing dan kompilasi ke bytecode akan dikerjakan kembali setiap kali halaman diakses pengguna!!

Salah satu cara untuk menghindari hal tersebut adalah dengan memakai teknologi caching (sering juga disebut sebagai PHP accelerator) seperti Alternative PHP Cache (APC) dan Zend Optimizer+. Karena saya menggunakan Zend Server 6.2.0, saya sudah memperoleh fasilitas Zend Optimizer+. Sebagai informasi, Zend Optimizer+ sudah terintegrasi pada PHP 5.5 sehingga tidak perlu di-install secara terpisah lagi. Ia mengalami perubahan nama menjadi OPcache dimana informasi lebih lanjut dapat ditemukan di http://www.php.net/manual/en/intro.opcache.php.

Zend Optimizer+ akan menampung hasil kompilasi file PHP dalam bentuk bytecode di memori. Bila selanjutnya ada request ke file PHP tersebut, Zend Optimizer+ akan langsung mengerjakan bytecode yang sudah ada di memori tanpa harus melalui proses parsing dan kompilasi lagi.

Untuk memeriksa apakah Zend Optimizer+ sudah diaktifkan, saya akan masuk ke dashboard Zend Server dengan membuka URL http://localhost:10081. Setelah memilih menu Configurations, Components, saya akan menemukan status loaded di Zend Optimizer+ seperti yang terlihat pada gambar berikut ini:

Melihat status Zend Optimizer+ di Zend Server

Melihat status Zend Optimizer+ di Zend Server

Pada halaman ini, saya juga dapat melakukan pengaturan lebih lanjut untuk Zend Optimizer+ dengan men-klik panan bawah di ujung kanan komponen tersebut, seperti yang terlihat pada gambar berikut ini:

Pengaturan Zend Optimizer+ di Zend Server

Pengaturan Zend Optimizer+ di Zend Server

Zend Optimizer+ akan bekerja secara transparan tanpa campur tangan pengguna. Namun, ia menyediakan API accelerator_reset() dan accelerator_get_status() yang dapat dipakai di kode program untuk melakukan reset dan memantau statistik. Sebagai contoh, berikut ini adalah kode program PHP yang menampilkan statistik untuk Zend Optimizer+:

<html>
<body>
<?php
    $stats = accelerator_get_status();

    print "<h3>Penggunaan Memori Untuk Zend Optimizer+</h3>";
    $memoriDipakai = round($stats['memory_usage']['used_memory'] / 1024 / 1024);
    $memoriBebas = round($stats['memory_usage']['free_memory'] / 1024 / 1024);
    $memoriWasted = round($stats['memory_usage']['wasted_memory'] / 1024 / 1024);
    $persenWasted = round($stats['memory_usage']['current_wasted_percentage'], 2);
    print "<p>Memori yang dipakai = $memoriDipakai MB</p>";
    print "<p>Memori yang invalid = $memoriWasted MB ($persenWasted %)</p>";
    print "<p>Memori bebas = $memoriBebas MB</p>";

    print "<h3>Statistik</h3>";
    print "<p>Jumlah script yang di-cache: {$stats['accelerator_statistics']['num_cached_scripts']}</p>";
    print "<p>Jumlah script yang dapat ditampung: {$stats['accelerator_statistics']['max_cached_scripts']}</p>";
    print "<p>Jumlah hit (cache yang dipakai): {$stats['accelerator_statistics']['hits']}</p>";
    print "<p>Jumlah miss (tidak ada dalam cache): {$stats['accelerator_statistics']['misses']}</p>";
    $hitRate = round($stats['accelerator_statistics']['accelerator_hit_rate'], 2);
    print "<p>Hit rate: $hitRate %</p>";
?>

</body>
</html>

Saat menjalankan file PHP di atas, saya akan memperoleh output seperti:

Penggunaan Memori Untuk Zend Optimizer+

Memori yang dipakai = 14 MB

Memori yang invalid = 0 MB (0 %)

Memori bebas = 50 MB

Statistik

Jumlah script yang di-cache: 622

Jumlah script yang dapat ditampung: 3907

Jumlah hit (cache yang dipakai): 18440

Jumlah miss (tidak ada dalam cache): 628

Hit rate: 96.71 %

Pada konfigurasi default, Zend Optimizer+ akan menggunakan memori sebesar 64 MB untuk menampung bytecode hasil kompilasi. Saya dapat mengubah nilai zend_optimizerplus.memory_consumption untuk meningkatkan jumlah memori yang dipakai bila perlu. Nilai hit rate menunjukkan indikator kinerja Zend Optimizer+, dimana nilai yang tinggi merupakan indikator kinerja yang baik.

Saya akan mencoba melihat peningkatkan kinerja yang diberikan oleh Zend Optimizer+ dengan mensimulasikan request dari 100 pengguna dimana masing-masing pengguna memberikan 10 request ke sebuah controller yang akan menampilkan sebuah view dengan memakai Twig. Seperti biasa, saya akan menggunakan Apache JMeter untuk mengukur waktu respon dari Zend Server. Tanpa Zend Optimizer+, saya memperoleh hasil seperti pada gambar berikut ini ini:

Hasil grafis tanpa Zend Optimizer+

Hasil grafis tanpa Zend Optimizer+

Hasil summary report terlihat seperti pada gambar berikut ini:

Hasil summary tanpa Zend Optimizer+

Hasil summary tanpa Zend Optimizer+

Kemudian, saya menjalankan ulang pengujian yang sama, tetapi kali ini dengan mengaktifkan Zend Optimizer+. Hasil grafis dari JMeter yang saya peroleh adalah:

Hasil grafis dengan Zend Optimizer+

Hasil grafis dengan Zend Optimizer+

Hasil summary report kali ini terlihat seperti pada gambar berikut ini:

Hasil summary dengan Zend Optimizer+

Hasil summary dengan Zend Optimizer+

Bila saya membandingkan nilai throughput kedua hasil di atas, saya akan menemukan selisih yang cukup jauh. Mengaktifkan Zend Optimizer+ meningkatkan throughput dari 11,3/sec menjadi 41,6/sec. Peningkatkan yang dicapai hampir mendekati 4 kali lipat. Tentunya peningkatan kinerja akan lebih terasa lagi bila terdapat semakin banyak file yang di-include (seperti saat memakai framework atau CMS).

Mencoba Memakai Standard PHP Library (SPL)

Pada pelajaran di masa perkuliahan, pelajaran dasar bagi mahasiswa biasanya adalah bahasa pemograman. Tapi sebuah bahasa pemograman tidak akan pernah sempurna bila tidak ada dukungan library atau API untuknya. Sebuah library adalah kumpulan function atau class yang dapat dipakai ulang dalam setiap proyek. Setiap bahasa pemograman memiliki filosofinya masing-masing mengenai apa yang harus menjadi bagian dari standard library. Sebagai contoh, pembuat C dan C++ memiliki filosofi hanya menyertakan sesuatu yang benar-benar sering dibutuhkan oleh seluruh programmer (bukan hanya programmer di bidang tertentu). Oleh sebab itu, standard library pada bahasa mereka berukuran kecil dan ringan. Perancang bahasa pemograman PHP memiliki filosofi lain lagi, yaitu menyediakan semua yang dibutuhkan oleh programmer web dalam library mereka sehingga programmer dapat membuat aplikasi web dengan nyaman.

Sesungguhnya PHP berkembang semakin kompleks dan mungkin semakin tidak terkendali ūüėČ Kode program yang menghasilkan apa yang disebut sebagai PHP dapat ditemukan di https://github.com/php/php-src. Interpreter PHP dibuat dengan menggunakan bahasa pemograman C bukan C++, jadi menguasai bahasa pemograman C adalah sebuah syarat mutlak untuk menjadi kontributor PHP. Situs http://www.phpinternalsbook.com yang masih belum rampung berusaha menjelaskan internal PHP sehingga mempermudah programmer yang tertarik mengembangkan PHP. Fitur pada PHP dikembangkan berdasarkan PHP RFC (Request For Comments) yang dapat ditemukan di https://wiki.php.net/rfc. Membuat RFC baru bukan hanya sekedar me-request feature baru, tetapi juga harus sanggup membuat implementasinya (setidaknya memahami dampaknya dalam internal PHP). RFC diawali dengan tahap diskusi melalui mailing list, bila lolos, akan masuk ke dalam tahap voting untuk diseleksi lebih lanjut. Sepertinya proses ini meniru Java Specification Request (JSR) di Java ūüôā

Dari sekian banyak library PHP (yang akan terus bertambah), saya menemukan apa yang disebut sebagai Standard PHP Library (SPL) di http://www.php.net/manual/en/intro.spl.php. Karena namanya mengandung standard library, saya berasumsi library in berisi segala sesuatu yang dibutuhkan untuk keperluan sehari-hari. Sejak versi PHP 5.3, SPL tidak dapat di-disabled seperti extension lainnya sehingga ia pasti selalu hadir di PHP manapun. Apa saja yang ada di SPL?

SPL menyediakan beberapa struktur data baru. Saya sudah pernah memakai struktur data SplObjectStorage untuk menampung object. Apa kelebihannya dibandingkan array biasa? SplObjectStorage menjaga tidak ada object ganda. Selain itu, untuk menghapus sebuah object dari struktur data, cukup dengan memanggil detach($object). Struktur data lainnya adalah SplQueue untuk mewakili queue, SplStack untuk mewakili stack, SplDoublyLinkedList untuk mewakili doubly linked list, SplHeap untuk mewakili tree dan sebagainya.

SPL juga menyediakan beberapa iterator yang dipakai dalam perulangan foreach. Sebagai contoh, ada FilesystemIterator yang dapat dipakai untuk menjelajahi sebuah direktori. Kode program berikut akan menampilkan nama dan ukuran file/direktori di root direktori drive C:

$it = new FileSystemIterator("C:");
foreach ($it as $fileinfo) {
   printf("%30s%20u\n", $fileinfo->getFilename(), 
      $fileinfo->getSize());     
}

Sebuah iterator dapat di-filter lagi dengan menggunakan CallbackFilterIterator. Sebagai contoh, saya akan menyaring hasil FilesystemIterator di atas agar hanya mengembalikan nama file dan mengabaikan direktori, dengan kode program seperti:

$it = new CallbackFilterIterator(new FilesystemIterator("C:"), 
   function($current, $key, $iterator) { 
      return $iterator->isFile();
   }
);
foreach ($it as $fileinfo) {
   printf("%30s%20u\n", $fileinfo->getFilename(), 
      $fileinfo->getSize());     
}

LimitIterator dapat dipakai untuk membatasi iterator lainnya. Sebagai contoh, pada kode program dibawah ini, ArrayIterator seharusnya melakukan perulangan sebanyak 15 kali, tetapi LimitIterator menyebabkan perulangan hanya 10 kali saja:

$array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
$it = new LimitIterator(new ArrayIterator($array), 0, 10);
foreach ($it as $i) {
   print "$i\n";
}

Semua class yang mengimplementasikan interface Countable dapat dipakai dengan fungsi sizeof() dan count(). Selain itu, saya dapat mengimplementasikan Iterator sehingga class saya dapat dipakai pada struktur foreach. Alternatif lainnya, bila saya memiliki sudah memiliki iterator di dalam class, saya dapat men-ekposnya dengan meng-implementasi-kan interface IteratorAggregate. Sebagai contoh, pada kode program berikut ini, class Pembelian dapat dipakai pada sizeof()/count() dan juga mendukung perulangan dengan foreach:

class Pembelian implements Countable, IteratorAggregate {

    public $items;

    public function __construct() {
        $this->items = new SplObjectStorage();
    }

    public function tambahItem($kodeBrg, $jumlah) {
        $this->items->attach(new Item($kodeBrg, $jumlah));            
    }

    public function count() {
        $i=0;
        foreach ($this->items as $item) {
            $i+=$item->jumlah;
        }           
        return $i;          
    }

    public function getIterator() {
      // SplObjectStorage juga adalah sebuah Iterator
        return $this->items;     
    }

}

class Item {

    public $kodeBrg;
    public $jumlah;

    public function __construct($kodeBrg, $jumlah) {
        $this->kodeBrg = $kodeBrg;
        $this->jumlah = $jumlah;
    }

}

$invoice = new Pembelian;
$invoice->tambahItem("BRG1", 10);
$invoice->tambahItem("BRG2", 20);

$jumlah = count($invoice);
print "Jumlah barang dalam invoice adalah $jumlah\n\n";

print "Daftar item di invoice adalah:\n";
foreach ($invoice as $item) {
    print "{$item->kodeBrg} {$item->jumlah}\n";
}

Hal yang lebih menarik lagi adalah dengan mengimplementasikan interface ArrayAccess, saya dapat membuat class saya menjadi dapat dipakai seperti array. Sebagai contoh, saya akan membuat instance dari class Pembelian di atas dapat diakses seperti array, misalnya: $p[0] akan mengembalikan item pembelian pertama, $p[1] akan mengembalikan item pembelian kedua, dan seterusnya. Untuk itu, saya mengubah kode program menjadi seperti berikut ini:

class Pembelian implements Countable, IteratorAggregate, ArrayAccess {

   ...      

    public function offsetExists($offset) {
        return $offset < ($this->items->count() - 1);      
    }

    public function offsetGet($offset) {
        $this->items->rewind();
        foreach($this->items as $item) {
            if ($this->items->key() == $offset) {
                return $item;
            }
        }               
        return null;
    }

    public function offsetSet($offset, $value) {        
        // tidak diimplementasikan
    }

    public function offsetUnset($offset) {
        // tidak diimplementasikan      
    }

}

...

print "Item #1: {$invoice[0]->kodeBrg} \n";
print "Item #2: {$invoice[1]->kodeBrg}\n";   

SPL juga menyediakan interface untuk menerapkan observer design pattern yaitu SplSubject dan SplObserver. Pada design pattern ini di PHP, sebuah subject class dapat memiliki satu atau lebih class lain sebagai observer. Kemudian saat subject class memberikan notifikasi dengan memanggil notify(), maka method update pada seluruh class observer akan dikerjakan. Contoh penggunaan observer design pattern dapat dilihat di view Griffon (untuk melakukan binding) seperti saat JTextField berubah nilainya (user mengetik), maka nilai property di model akan di-update sesuatu dengan apa yang diketik.

Untuk melakukan operasi file secara OOP, SPL menyediakan class SplFileInfo, SplFileObject dan SplTempFileObject. Sebagai contoh, kode program berikut ini akan membuat file baru:

$file = new SplFileObject('C:/test.txt', 'w+');
$file->fwrite("Ini adalah isi file!");
$file->fflush(); 

SPL juga menyediakan class ArrayObject untuk memakai array di PHP secara OOP. Berikut ini adalah contoh kode program yang memakai ArrayObject:

$arr = new ArrayObject([1,7,3,1,2,6,8,3,4,5]);
$arr->asort();

foreach ($arr as $i) print "$i ";
// output: 1 1 2 3 3 4 5 6 7 8 

print "\n{$arr->count()}\n";
// output: 10

$arr->append(11);
$arr[] = 12;
foreach ($arr as $i) print "$i ";
// output: 1 1 2 3 3 4 5 6 7 8 11 12

print "\n{$arr->offsetGet(10)}\n";
print "{$arr[10]}\n";
// output:
// 11
// 11

$arr->offsetUnset(5);
$arr->offsetUnset(4);
foreach ($arr as $i) print "$i ";
// output:
// 1 1 3 3 4 5 7 8 11 12 

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.

Memakai Reflection API Di PHP

Reflection adalah kode program yang membaca struktur sebuah class dan memanipulasinya bila perlu. Pembuat framework atau library biasanya berusaha membuat kode program yang dapat dipakai se-fleksibel mungkin. Oleh sebab itu, mereka sering kali perlu menjawab pertanyaan seperti: apa nama class ini? ada property atau method yang ada di class ini? Reflection API dirancang untuk menjawab pertanyaan tersebut. Pada Java, saya dapat memakai reflection dengan menggunakan sebuah instance class bernama Class yang diperoleh dengan method getClass(). PHP juga menyediakan reflection API. Informasi mengenai reflection di PHP dapat dibaca di http://www.php.net/manual/en/intro.reflection.php.

Sebagai latihan, kali ini saya akan menerapkan reflection API untuk membuat kode program di artikel Memahami Overloading ala PHP. Saat ini, kode program di artikel tersebut hanya dapat diterapkan di domain class Pelanggan saja. Saya akan membuatnya menjadi sesuatu yang lebih fleksibel yang dapat diterapkan pada seluruh domain class yang ada.

Saya akan mulai dari method save(). Agar method save() menjadi fleksibel dan dapat dipakai di domain class lain, saya mengubahnya menjadi seperti berikut ini:

...
public function save() {
   $class = new \ReflectionClass(get_class());
   $namaTabel = $class->getShortName();
   $props = $class->getProperties(\ReflectionProperty::IS_PUBLIC);

   $daftarKolom = implode(',', array_map(function($prop) { 
      return $prop->name; 
   }, $props));

   $daftarParameter = implode(',', array_fill(0, sizeof($props), '?'));

   $daftarValue = array_map(function($prop) { 
      return $prop->getValue($this); 
   }, $props); 

   $db = new \PDO('mysql:host=localhost;dbname=exercises', 'snake', 'password');
   $st = $db->prepare("INSERT INTO $namaTabel($daftarKolom) VALUES ($daftarParameter)");     
   $st->execute($daftarValue);                           
}
...

Pada kode program di atas, saya membuat sebuah instance ReflectionClass yang merupakan pusat operasi reflection API. Sebagai contoh, saya bisa memperoleh nama class (tanpa namespace) dengan memanggil method getShortName() miliknya. Saya juga bisa memperoleh seluruh property untuk class dengan memanggil method getProperties() yang mengembalikan array yang berisi instance dari class ReflectionProperty. Bukan hanya itu, saya dapat memperoleh nilai sebuah property dengan memanggil method getValue() dari ReflectionProperty.

Berikutnya, saya juga mengubah method __callStatic() agar memakai reflection API, seperti yang terlihat pada kode program berikut ini:

...
public static function __callStatic($name, $args) {
   if (preg_match('/findBy([A-Z]\w*)/', $name, $matches)==1) {

      $class = new \ReflectionClass(get_class());
      $namaTabel = $class->getShortName();

      // Melakukan parsing nama method
      $expr = $matches[1];
      $fields = preg_split("/(And|Or)/", $expr, NULL, PREG_SPLIT_DELIM_CAPTURE);
      $sql = "SELECT * FROM $namaTabel WHERE ";
      for ($i=0; $i<sizeof($fields); $i++) {                
         $sql .= ' ' . $fields[$i] . ' = ? ';
         if (++$i < sizeof($fields) - 1) {
            $sql .= ' ' . $fields[$i];
         }  
      }

      // Melakukan query ke database
      $db = new \PDO('mysql:host=localhost;dbname=exercises', 'snake', 'password');
      $st = $db->prepare($sql);
      $st->execute($args);
      return $st->fetchAll(\PDO::FETCH_CLASS, $class->getName());                    
   }

   trigger_error("Method $name tidak ditemukan!", E_USER_ERROR);  
}
...

Agar lebih rapi, saya akan memindahkan kode program yang berkaitan dengan database ke sebuah class tersendiri. Untuk itu, saya membuat sebuah file baru dengan nama Database.php di folder util yang isinya seperti berikut ini:

<?php

namespace util;

class Database {

   private static $instance;

   public static function getInstance() {
      if (is_null(self::$instance)) {
         self::$instance = new Database();
         self::$instance->pdo = new \PDO('mysql:host=localhost;dbname=exercises', 'snake', 'password');
      }
      return self::$instance;
   }

   /**
    * Instance dari PDO untuk melakukan operasi database.
    * 
    * @var \PDO
    */
   public $pdo;

   private function __construct() {}

   public function execute($sql, $args = NULL) {
      $st = $this->pdo->prepare($sql);    
      $st->execute($args);    
   }

   public function executeFetchAll($sql, $class, $args = NULL) {
      $st = $this->pdo->prepare($sql);
      $st->execute($args);                   
      return $st->fetchAll(\PDO::FETCH_CLASS, $class);

   }

}

?>

Dengan demikian, saya bisa menyederhanakan kode program di Pelanggan.php menjadi seperti berikut ini:

<?php

namespace domain;

use util\Database;

class Pelanggan {

   ...

   public function save() {
      $class = new \ReflectionClass(get_class());
      $namaTabel = $class->getShortName();
      $props = $class->getProperties(\ReflectionProperty::IS_PUBLIC);

      $daftarKolom = implode(',', array_map(function($prop) { 
         return $prop->name; 
      }, $props));

      $daftarParameter = implode(',', array_fill(0, sizeof($props), '?'));

      $daftarValue = array_map(function($prop) { 
         return $prop->getValue($this); 
      }, $props); 

      Database::getInstance()->execute(
         "INSERT INTO $namaTabel($daftarKolom) VALUES ($daftarParameter)", 
         $daftarValue);    
   }

   public static function __callStatic($name, $args) {
      if (preg_match('/findBy([A-Z]\w*)/', $name, $matches)==1) {

         $class = new \ReflectionClass(get_class());
         $namaTabel = $class->getShortName();

         // Melakukan parsing nama method
         $expr = $matches[1];
         $fields = preg_split("/(And|Or)/", $expr, NULL, PREG_SPLIT_DELIM_CAPTURE);
         $sql = "SELECT * FROM $namaTabel WHERE ";
         for ($i=0; $i<sizeof($fields); $i++) {                
            $sql .= ' ' . $fields[$i] . ' = ? ';
            if (++$i < sizeof($fields) - 1) {
               $sql .= ' ' . $fields[$i];
            }  
         }

         // Melakukan query ke database
         return Database::getInstance()->executeFetchAll($sql, 
            $class->getName(), $args);
      }

      trigger_error("Method $name tidak ditemukan!", E_USER_ERROR);  
   }  

}

?>

Pertanyaan berikutnya: kode program sudah memakai reflection sehingga bersifat fleksibel, tapi bukankah setiap kali menambah domain class baru tetap harus men-copy paste kode program save() dan __callStatic() ke class baru tersebut? Untuk mengatasi masalah ini, saya dapat menggunakan salah satu fitur OOP yaitu inheritance. Sebuah class yang diturunkan dari class lain secara otomatis akan memiliki seluruh fitur dari class parent-nya. Dengan demikian, saya hanya perlu memastikan seluruh domain class diturunkan dari class DomainObject (pada file DomainObject.php) yang isinya seperti berikut ini:

<?php

namespace domain;

use util\Database;

abstract class DomainObject {

   public function save() {
      $class = new \ReflectionClass(get_class($this));
      $namaTabel = $class->getShortName();
      $props = $class->getProperties(\ReflectionProperty::IS_PUBLIC);

      $daftarKolom = implode(',', array_map(function($prop) {
         return $prop->name;
      }, $props));

      $daftarParameter = implode(',', array_fill(0, sizeof($props), '?'));

      $daftarValue = array_map(function($prop) {
         return $prop->getValue($this);
      }, $props);

      Database::getInstance()->execute(
         "INSERT INTO $namaTabel($daftarKolom) VALUES ($daftarParameter)",
         $daftarValue);
   }

   public static function __callStatic($name, $args) {
      if (preg_match('/findBy([A-Z]\w*)/', $name, $matches)==1) {

         $class = new \ReflectionClass(get_called_class());
         $namaTabel = $class->getShortName();

         // Melakukan parsing nama method
         $expr = $matches[1];
         $fields = preg_split("/(And|Or)/", $expr, NULL, PREG_SPLIT_DELIM_CAPTURE);
         $sql = "SELECT * FROM $namaTabel WHERE ";
         for ($i=0; $i<sizeof($fields); $i++) {
            $sql .= ' ' . $fields[$i] . ' = ? ';
            if (++$i < sizeof($fields) - 1) {
               $sql .= ' ' . $fields[$i];
            }
         }

         // Melakukan query ke database
         return Database::getInstance()->executeFetchAll($sql,
               $class->getName(), $args);
      }

      trigger_error("Method $name tidak ditemukan!", E_USER_ERROR);
   }

}

?>

Perhatikan bahwa saya mengubah get_class() menjadi get_class($this) agar bisa memperoleh reflection dari subclass (class turunan dari DomainObject). Karena method __callStatic() bersifat static, maka saya tidak dapat menggunakan get_class(). Sebagai alternatifnya, saya menggunakan get_called_class() yang diperkenalkan sejak PHP 5.3.

Selain itu, saya juga memberikan keyword abstract pada class DomainObject karena class ini tidak boleh diciptakan (dibuat instance-nya). Ia wajib harus dipakai melalui turunannya, seperti Pelanggan, Produk, Order, dan sebagainya.

Sekarang, saya dapat mengubah class Pelanggan agar diturunkan dari DomainObject dengan memakai keyword extends seperti pada kode program berikut ini:

<?php

namespace domain;

class Pelanggan extends DomainObject {

   public $nama;

   public $alamat;

   public $usia;     

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

}

?>

Bila saya menjalankan aplikasi PHP ini, method save() dan finders tetap bekerja dengan baik. Sekarang, saya juga dapat menambah domain model baru dengan mudah. Sebagai contoh, saya akan membuat sebuah class baru bernama Produk di file \domain\Produk.php yang isinya seperti berikut ini:

<?php

namespace domain;

class Produk extends DomainObject {

   public $kode;

   public $nama;

   public $harga;

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

}

?>

Setelah itu, saya membuat sebuah tabel dengan SQL seperti berikut ini:

CREATE TABLE produk (
   kode CHAR(7) PRIMARY KEY,
   nama VARCHAR(100) NOT NULL,
   harga INT NOT NULL
);

Berkat inheritance, class Produk secara otomatis memiliki method save() dan finders. Saya dapat membuktikannya dengan kode program seperti berikut ini:

<?php

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

   use domain\Pelanggan;
   use domain\Produk;

   $snake = new Pelanggan("Solid Snake", "Alaska", 35);
   $snake->save();
   $liquid = new Pelanggan("Liquid Snake", "New York", 35);
   $liquid->save();
   $boss = new Pelanggan("The Boss", "Alaska", 40);
   $boss->save();

   $produk1 = new Produk("PR-0001", "Produk #1", 100000);
   $produk1->save();
   $produk2 = new Produk("PR-0002", "Produk #2", 150000);
   $produk2->save();

?>

<pre>
<?php

   print "Daftar pelanggan dengan nama Solid Snake di Alaska:\n";
   print_r(Pelanggan::findByNamaAndAlamat("Solid Snake", "Alaska"));

   print "\nDaftar pelanggan dengan usia 35 tahun:\n";
   print_r(Pelanggan::findByUsia(35));

   print "\nDaftar pelanggan yang tinggal di Alaska:\n";
   print_r(Pelanggan::findByAlamat("Alaska"));

   print "\nDaftar Produk Dengan Kode PR-0001:\n";
   print_r(Produk::findByKode("PR-0001"));

   print "\nDaftar Produk Dengan Nama 'Produk #2':\n";
   print_r(Produk::findByNama("Produk #2"));

?> 
</pre>   

Kode program di atas akan menyisipkan beberapa record ke tabel Pelanggan dan Produk, serta melakukan query ke tabel tersebut. Reflection API berperan penting membantu saya agar tidak perlu men-hard code operasi SQL ke dalam masing-masing class. Reflection API memungkinkan saya untuk menghasilkan kode program yang fleksibel dan reusable.

Memahami Overloading ala PHP

PHP tidak mendukung overloading!!! Yup! Dalam benak programmer Java, C++, C# dan sejenisnya, overloading adalah kemampuan untuk mendeklarasikan beberapa method dengan nama yang sama tetapi masing-masing memiliki jumlah dan tipe argumen yang berbeda. PHP tidak membolehkan hal tersebut!! Lalu mengapa di dokumentasi PHP terdapat sebuah halaman yang menjelaskan tentang overloading di PHP: http://php.net/__callstatic?

Apa yang disebut overloading oleh PHP adalah sesuatu yang mirip seperti methodMissing() di Groovy yang sering dipakai untuk keperluan meta programming. PHP menyediakan magic method seperti __set(), __get(), __isset(), __unset(), __call() dan __callStatic(). Mereka akan dipanggil bila programmer mengakses property atau method yang belum pernah dideklarasikan sebelumnya. Tentu saja overloading dengan cara seperti ini tentunya jauh lebih repot dibandingkan dengan Java dan C++, terutama bila yang perlu di-overload hanya beberapa method. Sebagai contoh, saya dapat melakukan ini di Java:

public class Pelanggan {

   public void pesan(Item item) {}

   public void pesan(List<Item> items) {}

   public void pesan(Item item, Integer jumlah, BigDecimal harga) {}

}

Untuk mencapai hal serupa di PHP, saya dapat menggunakan kode program seperti berikut ini:

<?php

class Pelanggan {

  public function __call($name, $arguments) {
    if ($name=='pesan') {
      switch (sizeof($arguments)) {
        case 1:
          if ($arguments[0] instanceof Item) {
            // kode program untuk pesan(Item item)
            print "pesan({$arguments[0]})<br>";
          } else if (is_array($arguments[0])) {
            // kode program untuk pesan(List items)
            print "pesan({$arguments[0]})<br>";
          }
          break;
        case 3:
          // kode program untuk pesan(Item item, Integer jumlah, BigDecimal harga)
          print "pesan({$arguments[0]},{$arguments[1]},{$arguments[2]})<br>";
          break;
      }
    }
  }

}

$pelanggan = new Pelanggan();
$pelanggan->pesan("ITEM1");
$pelanggan->pesan(["ITEM1", "ITEM2", "ITEM3"]);
$pelanggan->pesan("ITEM1", 10, 20000); 

?>

Beruntungnya, PHP adalah bahasa pemograman dinamis sehingga overloading bukanlah sesuatu yang sering dibutuhkan.

Karena apa yang disebut overloading di PHP lebih tepat disebut sebagai meta programming, maka saya bisa membuat dynamic finders (seperti di simple-jpa) di bahasa pemograman PHP. Perlu diperhatikan bahwa hal ini akan memberikan dampak buruk di sisi kinerja karena penggunaan magic method lebih lambat bila dibandingkan dengan mendeklarasikan method atau property secara langsung.

Sebagai contoh, saya membuat sebuah domain class sederhana di folder domain dengan nama Pelanggan.php yang isinya seperti berikut ini:

<?php
namespace domain;

class Pelanggan {

   public $nama;

   public $alamat;

   public $usia;     

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

   public function save() {      
      $db = new \PDO('mysql:host=localhost;dbname=exercises', 'snake', 'password');
      $st = $db->prepare("INSERT INTO Pelanggan(nama, alamat, usia) VALUES (?, ?, ?)");      
      $st->execute([$this->nama, $this->alamat, $this->usia]);                           
   }

}
?>

Pada kode program diatas, saya mendefinisikan sebuah class dengan nama Pelanggan. Class ini memiliki sebuah constructor untuk melakukan inisialisasi property-nya. Pada PHP, constructor juga adalah magic method. Saya melakukan penjagaan terhadap NULL di constructor karena saat saya melakukan fetching di PDO dengan PDO::FETCH_CLASS, kode program di constructor akan dikerjakan dengan argumen serba NULL setelah property diberi nilai. Ini adalah perilaku constructor yang tidak lazim dan saya perlu mewaspadainya.

Saya juga membuat sebuah method save() yang akan menyimpan data ke database dengan menggunakan PDO (PHP Data Object). PDO memungkinkan saya untuk mengakses database secara OOP. Selain itu, PDO adalah abstraction layer. Tanpa PDO, saya perlu mengakses database secara langsung dengan fungsi mysqli_xxx() yang hanya berlaku untuk database MySQL. Dengan PDO, jika suatu saat ini saya harus beralih ke database lain (misalnya Oracle), saya cukup perlu mengubah DSN (sebuah String yang isinya seperti 'mysql:host=localhost;dbname=exercises'). Mirip seperti JDBC di Java ‘bukan? Daftar database yang didukung (PDO driver) dapat dilihat di http://php.net/manual/en/pdo.drivers.php.

Pada method save(), saya juga memakai syntax baru di PHP 5.4 untuk mendeklarasikan array. Sebelum versi 5.4, array di PHP harus didefinsikan dengan keyword array seperti array(10, 20, 30, 40). PHP 5.4 membolehkan definisi array dengan cara seperti [10, 20, 30, 40]. Selain lebih sederhana, syntax baru ini juga adalah syntax deklarasi array di banyak bahasa dinamis lain seperti Groovy, Ruby, dan Python.

Berikutnya, saya akan menambahkan dynamic finders pada class Pelanggan. Karena finders tersebut dapat dipanggil kapan saja tanpa harus ada instance dari class, maka mereka harus berupa method static. PHP 5.3 memiliki __callStatic() yang membolehkan overloading (ingat bahwa ini istilah PHP sendiri!) method static. Berikut adalah isi method tersebut:

class Pelanggan {

   ...

   public static function __callStatic($name, $args) {
      if (preg_match('/findBy([A-Z]\w*)/', $name, $matches)==1) {

         // Melakukan parsing nama method
         $expr = $matches[1];
         $fields = preg_split("/(And|Or)/", $expr, NULL, PREG_SPLIT_DELIM_CAPTURE);
         $sql = 'SELECT * FROM Pelanggan WHERE ';
         for ($i=0; $i<sizeof($fields); $i++) {                
            $sql .= ' ' . $fields[$i] . ' = ? ';
            if (++$i < sizeof($fields) - 1) {
               $sql .= ' ' . $fields[$i];
            }  
         }

         // Melakukan query ke database
         $db = new \PDO('mysql:host=localhost;dbname=exercises', 'snake', 'password');
         $st = $db->prepare($sql);
         $st->execute($args);
         return $st->fetchAll(\PDO::FETCH_CLASS, '\domain\Pelanggan');                    
      }

      trigger_error("Method $name tidak ditemukan!", E_USER_ERROR);  
   }  

   ...

}
?>

Pada method di atas, saya menggunakan fetchAll() dari PDO dengan fecth style berupa PDO::FETCH_CLASS. Hal ini akan menyebabkan method fetchAll() mengembalikan sebuah array yang berisi instance dari class yang saya tentukan, yaitu Pelanggan. PDO akan mengisi property berdasarkan nama kolom di tabel.

Berikutnya, saya akan membuat sebuah tabel di database dengan perintah SQL berikut ini:

CREATE TABLE Pelanggan (
   nama VARCHAR(50) PRIMARY KEY,
   alamat VARCHAR(100),
   usia INT NOT NULL
);

Lalu, saya dapat memakai domain class saya, misalnya seperti berikut ini:

<?php

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

  use domain\Pelanggan;

  $snake = new Pelanggan("Solid Snake", "Alaska", 35);
  $snake->save();
  $liquid = new Pelanggan("Liquid Snake", "New York", 35);
  $liquid->save();
  $boss = new Pelanggan("The Boss", "Alaska", 40);
  $boss->save();

?>

<pre>
<?php

  print "Daftar pelanggan dengan nama Solid Snake di Alaska:\n";
  print_r(Pelanggan::findByNamaAndAlamat("Solid Snake", "Alaska"));

  print "\nDaftar pelanggan dengan usia 35 tahun:\n";
  print_r(Pelanggan::findByUsia(35));

  print "\nDaftar pelanggan yang tinggal di Alaska:\n";
  print_r(Pelanggan::findByAlamat("Alaska"));

?>   
</pre>

Pada kode program di atas, saya menggunakan spl_autoload_extensions() dan spl_autoload_register() agar PHP secara otomatis men-include file class saat saya memakainya. Ingat bahwa saya mendefinisikan class pada file PHP yang terpisah. Tanpa kedua fungsi di atas, saya harus menyertakan file domain class saya dengan menggunakan keyword include atau require. Dengan fungsi spl_autoload_xxx() yang sudah ada sejak PHP 5.3, pada saat sebuah class hendak dipakai tetapi deklarasinya tidak ditemukan, maka autoloader akan bekerja mencari & membaca file yang tepat. Pada kode program di atas, autoloader akan mencari file di domain\Pelanggan.php bila menemukan penggunaan class \domain\Pelanggan. Konsekuensinya adalah sebuah class harus didefinisikan pada sebuah file PHP dengan nama yang sama dengan nama class. Setiap lokasi namespace juga harus diwakili dengan sebuah direktori/folder. Mirip seperti package di Java, bukan?

Kode program di atas akan menciptakan tiga record baru di tabel, lalu menggunakan finders untuk melakukan query, dimana hasilnya akan terlihat seperti berikut ini:

Daftar pelanggan dengan nama Solid Snake di Alaska:
Array
(
    [0] => domain\Pelanggan Object
        (
            [nama] => Solid Snake
            [alamat] => Alaska
            [usia] => 35
        )

)

Daftar pelanggan dengan usia 35 tahun:
Array
(
    [0] => domain\Pelanggan Object
        (
            [nama] => Liquid Snake
            [alamat] => New York
            [usia] => 35
        )

    [1] => domain\Pelanggan Object
        (
            [nama] => Solid Snake
            [alamat] => Alaska
            [usia] => 35
        )

)

Daftar pelanggan yang tinggal di Alaska:
Array
(
    [0] => domain\Pelanggan Object
        (
            [nama] => Solid Snake
            [alamat] => Alaska
            [usia] => 35
        )

    [1] => domain\Pelanggan Object
        (
            [nama] => The Boss
            [alamat] => Alaska
            [usia] => 40
        )

)

Anonymous Function Di PHP

PHP sejak versi 5.3 sudah mendukung anonymous function dan closure.  Sementara itu, Java hingga versi 7 masih belum mendukung closure secara penuh (selain dengan variabel final).  Fitur ini akan disertakan pada Java 8 nanti dengan memakai lambda expression.

Apa itu closure?  Artikel Memakai Closure Di JavaScript menjelaskan tentang closure dan contoh penerapannya di JavaScript.  Pada tulisan ini, saya akan mencoba menggunakan fitur closure di PHP.

Sebagai contoh, saya ingin menghasilkan menu dari array berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");
?>

Saya mendefinisikan sebuah anonymous function yang akan menghasilkan HTML berdasarkan string di setiap elemen array, lalu menyimpan anonymous function tersebut ke sebuah variabel, seperti pada kode program berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");

  $prosesMenu = function($namaMenu, $indexMenu) {
     print "<div style='background-color: #00ccff; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }
?>

Sekarang, saya dapat memproses setiap elemen array di $daftarMenu dengan anonymous function $prosesMenu dengan menggunakan array_walk seperti pada kode program berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");  
  $prosesMenu = function($namaMenu, $indexMenu) {
     print "<div style='background-color: #00ccff; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }

  array_walk($daftarMenu, $prosesMenu);
?>

Pada contoh di atas, saya baru menggunakan anonymous function dan belum memakai closure.  Sekarang, seandainya saya ingin memberikan pewarnaan yang berbeda untuk menu yang sedang aktif, maka saya perlu melakukan perubahan kode program sehingga terlihat seperti berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");  
  $prosesMenu = function($namaMenu, $indexMenu, $menuAktif) {
     if ($menuAktif==$indexMenu) {
        $backgroundColor = "#cc00ff";
     } else {
        $backgroundColor = "#00ccff";
     }
     print "<div style='background-color: $backgroundColor; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }

  array_walk($daftarMenu, $prosesMenu);
?>

Bila program PHP tersebut dijalankan, menu pertama (index 0) akan selalu di-highlight karena nilai $menuAktif selalu adalah 0.  Seandainya saya bisa mendefinisikan $menuAktif  secara langsung, apakah saya bisa memberikan kode program berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");

  $menuAktif = 1;

  $prosesMenu = function($namaMenu, $indexMenu, $menuAktif) {
     if ($menuAktif==$indexMenu) {
        $backgroundColor = "#cc00ff";
     } else {
        $backgroundColor = "#00ccff";
     }
     print "<div style='background-color: $backgroundColor; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }

  array_walk($daftarMenu, $prosesMenu);
?>

Ternyata variabel $menuAktif belum dapat diakses secara langsung oleh anonymous function!  Menu dengan index 1 (urutan kedua) tidak akan di-highlight.  PHP mensyaratkan penggunaan keyword use bila ingin memakai variabel di parent scope di closure.  Dengan demikian, saya harus mengubah kode program di atas menjadi:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");

  $menuAktif = 1;

  $prosesMenu = function($namaMenu, $indexMenu, $menuAktif) use ($menuAktif) {
     if ($menuAktif==$indexMenu) {
        $backgroundColor = "#cc00ff";
     } else {
        $backgroundColor = "#00ccff";
     }
     print "<div style='background-color: $backgroundColor; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }

  array_walk($daftarMenu, $prosesMenu);
?>

Sekarang nilai $menuAktif di dalam anonymous function adalah 1.  Dengan closure, saya dapat memakai variabel $menuAktif di dalaman sebuah anonymous function walaupun variabel $menuAktif bukanlah variabel global.

COM DLL Akses MySQL ‚Äď Bagian 3: Operasi Database Insert

Tulisan ini merupakan bagian dari panduan membuat COM DLL (ATL) di Visual Studio 2010 yang mengakses MySQL atas request Albert Antonius:

Mari mulai dengan membuat sebuah ATL Object. ¬†Caranya adalah dengan membuka Class View. ¬†Bila tab Class View tidak muncul, pilih menu View, Class View. ¬†Klik kanan pada LatihanCOM, dan pilih Add, Class… Pada dialog yang muncul, pilih ATL Simple Object kemudian klik tombol Add. Isi dialog ATL Simple Object Wizard dengan memberi nama Mahasiswa pada Short name serta Latihan.Mahasiswa pada ProgID seperti yang terlihat di gambar berikut ini:

Membuat ATL Object Baru

Membuat ATL Object Baru

Klik tombol Finish. Bila kamu masih bingung dengan langkah ini, coba baca kembali artikel pengenalan di sini.

Sekarang kita akan menambahkan property DBUser untuk menampung user name yang dipakai untuk akses database nantinya. Masih di Class View, klik kanan pada interface IMahasiswa, pilih Add, Add Property… Isi Property Type dengan BSTR dan isi Property name dengan DBUser seperti yang terlihat pada gambar berikut:

Membuat Property DBUser

Membuat Property DBUser

Klik tombol Finish.

Lakukan hal yang sama untuk menambahkan property DBPassword yang menampung password untuk akses database MySQL, seperti yang terlihat pada gambar berikut:

Membuat Property DBPassword

Membuat Property DBPassword

Klik tombol Finish.

Kembali lakukan hal yang sama untuk menambahkan property DBNama yang menampung nama database MySQL yang akan dipakai, seperti yang terlihat pada gambar berikut:

Membuat Property DBNama

Membuat Property DBNama

Klik tombol Finish.

Apa itu BSTR? Tebakanmu benar, BSTR adalah tipe data String versi COM. Ingat bahwa COM adalah teknologi yang tidak terikat oleh bahasa. Padahal, hampir setiap bahasa memiliki tipe data String tersendiri, misalnya di bahasa C memakai char*, bahasa C++ memakai class string, bahasa Java memakai class java.lang.String, dan sebagainya.

Sekarang kita akan membuat deklarasi method, yaitu method Inisialisasi. Method ini akan melakukan pemeriksaan database dan membuat tabel yang dibutuhkan. Caranya adalah dengan klik kanan pada interface IMahasiswa, pilih Add, Add Methods… Pada dialog yang muncul, isi Method name dengan Inisialisasi, seperti yang terlihat pada gambar berikut ini:

Membuat Method Inisialisasi

Membuat Method Inisialisasi

Klik tombol Finish.

Lalu buat method lain, yaitu method TambahMahasiswa. Caranya adalah dengan klik kanan pada interface IMahasiswa, lalu pilih Add, Add Method… Pada dialog yang muncul, isi Method name dengan TambahMahasiswa. Berikan tiga parameter [in] berupa BSTR nim, BSTR nama, dan int tahunMasuk seperti yang terlihat pada gambar berikut ini:

Membuat Method TambahMahasiswa

Membuat Method TambahMahasiswa

Klik tombol Finish.

Bila kamu melakukan langkah-langkah yang ada dengan benar, di file LatihanCOM.idl, kamu akan menemukan bagian berikut ini:

interface IMahasiswa : IDispatch{
	[propget, id(1)] HRESULT DBUser([out, retval] BSTR* pVal);
	[propput, id(1)] HRESULT DBUser([in] BSTR newVal);
	[propget, id(2)] HRESULT DBPassword([out, retval] BSTR* pVal);
	[propput, id(2)] HRESULT DBPassword([in] BSTR newVal);
	[propget, id(3)] HRESULT DBNama([out, retval] BSTR* pVal);
	[propput, id(3)] HRESULT DBNama([in] BSTR newVal);
	[id(4)] HRESULT TambahMahasiswa([in] BSTR nim, [in] BSTR nama, [in] int tahunMasuk);
	[id(5)] HRESULT Inisialisasi(void);
};

Seperti yang kamu ketahui, interface hanya sebuah kontrak tanpa isi. Interface adalah sebuah janji tanpi aksi. Oleh sebab itu, kamu perlu membuat implementasi dari interface IMahasiswa.

Tapi sebelumnya, masih ingat kamu membuat tiga property, yaitu DBUser, DBPassword, dan DBNama? Kamu perlu membuat tiga variabel untuk menampung masing-masing nilai tersebut. Kamu bisa mengubah definisi class secara langsung di file Mahasiswa.h, tetapi biar gampang, kamu dapat menambahkan variabel melalui wizard. Pastikan kamu masih berada di Class View. Klik kanan pada class CMahasiswa, pilih Add, Add Variable… Pada Access, plih private. Pada Variable type, ketik char[100]. Pada Variable name, isi dengan m_dbUser, sehingga dialog akan terlihat seperti:

Membuat private member variable

Membuat private member variable

Klik tombol Finish.

Lakukan hal yang sama untuk membuat variabel m_dbPassword dan m_dbNama. Kedua variabel tersebut juga sama-sama bertipe char[100]. Yup, kita membatasi hingga maksimal 100 karakter.

Setelah interface dan variabel selesai dibuat, pertanyaannya adalah dimana kamu meletakkan implementasi kode program?  Buka Solution Explorer (bila kamu tersesat, klik menu View, Solution Explorer). Kemudian double click pada file Mahasiswa.cpp. Disini adalah tempat kamu meletakkan isi dari method untuk class CMahasiswa.

Kita akan mulai dengan get/put untuk property yang ada. Ganti method yang bersangkutan sehingga isinya terlihat seperti berikut ini:

size_t len;

STDMETHODIMP CMahasiswa::get_DBUser(BSTR* pVal)
{
	CComBSTR bstr(m_dbUser);	
	*pVal = bstr.Detach();
	return S_OK;
}

STDMETHODIMP CMahasiswa::put_DBUser(BSTR newVal)
{	
	wcstombs_s(&len, m_dbUser, newVal, 99);	
	return S_OK;
}

STDMETHODIMP CMahasiswa::get_DBPassword(BSTR* pVal)
{
	CComBSTR bstr(m_dbPassword);
	*pVal = bstr.Detach();
	return S_OK;
}

STDMETHODIMP CMahasiswa::put_DBPassword(BSTR newVal)
{	
	wcstombs_s(&len, m_dbPassword, newVal, 99);
	return S_OK;
}

STDMETHODIMP CMahasiswa::get_DBNama(BSTR* pVal)
{
	CComBSTR bstr(m_dbNama);
	*pVal = bstr.Detach();
	return S_OK;
}

STDMETHODIMP CMahasiswa::put_DBNama(BSTR newVal)
{	
	wcstombs_s(&len, m_dbNama, newVal, 99);
	return S_OK;
}

Saya menambahkan sebuah deklarasi size_t len karena variabel yang sama ini akan dipakai pada saat pemanggilan fungsi wcstombs_s(). Btw, di sini saya menggunakan class CComBSTR milik framework ATL untuk melakukan konversi char* (C string) menjadi BSTR. Sebaliknya, untuk melakukan konversi BSTR menjadi char* (C string), saya menggunakan fungsi wcstombs_s().
Berikutnya, saya akan menambahkan deklarasi method Inisialisasi. Tapi karena akan memakai Connector/CPP, maka saya menambahkan baris berikut ini di bagian atas di class Mahasiswa.cpp:

#include "mysql_connection.h"
#include <cppconn\driver.h>
#include <cppconn\exception.h>
#include <cppconn\prepared_statement.h>
#include <cppconn\statement.h>

using namespace sql;

Lalu, saya mengubah method Inisialisasi sehingga terlihat seperti berikut ini:

STDMETHODIMP CMahasiswa::Inisialisasi(void)
{
	Driver* driver;
	Connection* con;
	Statement* st;

	driver = get_driver_instance();
	con = driver->connect("tcp://127.0.0.1:3306", m_dbUser, m_dbPassword);
	con->setSchema(m_dbNama);

	st = con->createStatement();
	st->execute("CREATE TABLE IF NOT EXISTS tblMahasiswa (nim CHAR(10), nama VARCHAR(200), tahunMasuk INT)");
	delete st;
	delete con;

	return S_OK;
}

Method ini akan secara otomatis membuat sebuah tabel dengan nama tblMahasiswa bila tabel tersebut belum ada. Biar cepat, saya mengandaikan bahwa server MySQL berada di komputer yang sama (IP 127.0.0.1). Saya juga mengabaikan penanganan kesalahan disini.

Sekarang, ubah method TambahMahasiswa sehingga terlihat seperti berikut ini:

STDMETHODIMP CMahasiswa::TambahMahasiswa(BSTR nim, BSTR nama, int tahunMasuk)
{
	Driver* driver;
	Connection* con;
	PreparedStatement* ps;
	char pNim[11];
	char pNama[201];
	wcstombs_s(&len, pNim, nim, 10);
	wcstombs_s(&len, pNama, nama, 200);

	driver = get_driver_instance();
	con = driver->connect("tcp://127.0.0.1:3306", m_dbUser, m_dbPassword);
	con->setSchema(m_dbNama);

	ps = con->prepareStatement("INSERT INTO tblMahasiswa(nim, nama, tahunMasuk) VALUES (?,?,?)");
	ps->setString(1, pNim);
	ps->setString(2, pNama);
	ps->setInt(3, tahunMasuk);
	ps->executeUpdate();

	delete ps;
	delete con;

	return S_OK;
}

Langkah terakhir untuk COM kita adalah membuatnya dengan memilih menu Build, Build Solution. Jika tidak ada pesan kesalahan, maka kita siap untuk lanjut ke tahap berikutnya.

Yang perlu kamu lakukan berikutnya adalah membuat sebuah database baru di MySQL Server. Kamu juga boleh memakai database yang sudah ada. Kamu dapat membuat database baru dengan menggunakan PHPMyAdmin, dan jika kamu terbiasa memakai MySQL Console, maka kamu dapat memberikan perintah:

CREATE DATABASE nama_database;
GRANT ALL ON nama_database.* to nama_user@`127.0.0.1`;

Wow.. Perjalanan yang panjang,  bukan? Tapi percayalah, dibalik sesuatu yang rumit selalu ada kesederhanaan. Kamu sudah membuat bagian rumit-nya, sekarang saatnya menikmati hasil jerih payah kamu.

Buat sebuah halaman PHP dengan seperti berikut ini:

<?php
 if (isset($_GET['txtNIM']) && isset($_GET['txtNama']) && 
 isset($_GET['txtTahunMasuk'])) {

 $com = new COM("Latihan.Mahasiswa");
 $com->DBUser = "root";
 $com->DBPassword = "password";
 $com->DBNama = "latihan";
 $com->Inisialisasi();

 $nim = $_GET['txtNIM'];
 $nama = $_GET['txtNama'];
 $tahunMasuk = $_GET['txtTahunMasuk'];
 $com->TambahMahasiswa($nim, $nama, $tahunMasuk);
}
?>
<html>
<head><title>Latihan Insert</title></head>
<body>
<form action="<?php print $_SERVER['PHP_SELF'] ?>" method="GET">
 <p>NIM: <input type="text" name="txtNIM" size="10" /></p>
 <p>Nama: <input type="text" name="txtNama" size="20" /></p>
 <p>Tahun Masuk: <input type="text" name="txtTahunMasuk" size="10" /></p>
 <p><input type="submit" value="Simpan" /></p>
</form>
</body>
</html>

Pada kode program di atas, jangan lupa menyesuaikan user, password, dan nama database.

Sekarang,  buka halaman PHP tersebut di browser, isi data pada form yang muncul dan klik tombol Simpan. Kemudian periksa isi tabel tblMahasiswa dengan menggunakan PHPMyAdmin ataupun MySQL Console. Jika kamu mengikuti langkah yang ada dengan benar, maka data yang kamu masukkan akan tersimpan ke tabel tblMahasiswa.

Kode program PHP yang mengakses database terlihat sederhana & singkat, bukan? Tapi membuat COM DLL-nya terasa penuh perjuangan, bukan? Lain kali bila kamu melihat sesuatu yang sederhana, kamu mungkin telah sadar bahwa dibaliknya pasti ada yang bekerja keras menyembunyikan kerumitannya.  Sebenarnya, banyak kerumitan bisa dihindari, misalnya bila kamu memakai ADO atau memakai DLL .NET, yang nantinya tidak beda bila memakai Java dengan Web Service (SOAP).  Tapi tidak setidaknya, kamu sudah merasakan sesuatu yang low-level (semakin low-level semakin rumit, sehingga muncullah teknologi high-level untuk menyembunyikan kerumitan dan membuat orang-orang lupa bahwa low-level sebenarnya eksis).

Memanggil web service dari server Java EE di client PHP

Seorang mahasiswa yang sedang membuat skripsi bertanya apakah mungkin membuat sebuah web service dengan Java Enterprise Edition kemudian memanggilnya di client PHP?  Pada kesempatan tatap muka yang singkat, saya menjawab secara ringkas dengan merujuk pada definisi web services. Web services adalah sistem yang dirancang secara khusus untuk mendukung interaksi pertukaran data mesin ke mesin melalui jaringan. Selama berkomunikasi melalui metode yang sama, maka proses komunikasi dapat terjadi tanpa membedakan OS maupun bahasa pemograman yang dipakai.   Saat ini ada dua jenis web services, yaitu yang berbasis REST dan berbasis SOAP. Web services berbasis REST cenderung lebih ringan dan lebih mudah dipelajari dibandingkan dengan SOAP. Hal ini menyebabkan terjadinya peralihan dari web services berbasis SOAP ke REST.

Pada kesempatan tertulis ini, saya akan memberikan sebuah contoh halaman PHP yang memanggil web services yang dibuat dengan Java Enterprise Edition. Metode yang dipergunakan adalah metode berbasis SOAP.

Saya akan membuat dua proyek, yaitu:

  1. Server web service yang menggunakan teknologi Java Enterprise Edition. Saya menggunakan NetBeans IDE dan GlassFish bawaannya.
  2. Sebuah halaman PHP yang mengakses layanan web service yang disediakan oleh server di atas.

Membuat Server Web Service dengan Java EE

Langkah-langkah yang saya lakukan adalah:

  1. Memilih menu File, New Project di NetBeans IDE. Kemudian saya memilih Java Web di Categories, dan Web Application di Projects. Setelah memberi nama proyek dan menentukan lokasi penyimpanan, saya men-klik tombol Finish.
  2. Men-klik kanan nama proyek, kemudian memilih menu New, Other... Pada dialog yang muncul, saya memilih Web Services di bagian Categories, dan Web Service di bagian File Typesseperti yang terlihat di gambar berikut ini:

    Membuat Web Services Baru

    Membuat Web Services Baru

  3.  Pada Web Service Name, saya mengisi dengan nama PerhitunganWS. Pada bagian package, saya mengisi dengan nama package co.id.jocki.ws. Setelah itu, saya men-klik tombol Finish.

NetBeans akan membuat sebuah class baru yang berada di folder Web Services seperti yang terlihat pada gambar berikut ini:

Web Services Di Tampilan Project

Web Services Di Tampilan Project

Bila class PerhitunganWS di-buka, NetBeans memungkinkan pengguna untuk melihat dalam bentuk Source atau Design. Bila tampilan Design dipakai, maka layar editor akan terlihat seperti pada gambar berikut ini:

NetBeans Web Service Editor

NetBeans Web Service Editor

Untuk menambahkan sebuah operasi baru, saya melakukan langkah-langkah seperti berikut ini:

  1. Klik tombol Add Operation… Akan muncul sebuah dialog baru.
  2. Untuk menambah parameter, saya dapat men-klik tombol Add. Saya mengisi dialog tersebut seperti yang terlihat pada gambar berikut ini:

    Menambah Operasi Baru Di Web Service

    Menambah Operasi Baru Di Web Service

  3. Setelah itu, saya men-klik tombol OK.

Setelah ini, saya perlu menambahkan kode program yang berisi proses untuk operasi baru tersebut.   Saya men-klik Source di toolbar untuk beralih ke tampilan kode program.   Kemudian, saya mengubah satu-satunya baris di method tambah() menjadi return angka1 + angka2; seperti yang terlihat di gambar berikut ini:

Kode Program Operasi Di Web Service

Kode Program Operasi Di Web Service

Untuk menguji web service tersebut, klik kanan pada nama class PerhitunganWS, kemudian memilih Test Web Service seperti yang terlihat di gambar berikut ini:

Menguji Web Services

Menguji Web Service

NetBeans akan menjalankan browser yang berisi web service tester. Di halaman ini terdapat sebuah link bertuliskan WSDL File. URL disini nantinya akan dipakai oleh client web service. Di percobaan saya, nilai URL ini adalah http://localhost:8080/ServerWebService/PerhitunganWS?WSDL. Saya akan men-copy lokasi URL ini untuk dipakai di PHP nantinya.

Pada bagian methods, saya mencoba mengisi parameter dengan nilai 10 dan 20, kemudian setelah men-klik tombol tambah (nama method), akan muncul halaman tambah Method invocation. Pastikan pada halaman tersebut, terdapat tulisan Method returned int: “30”.

Sekarang server web service telah selesai dibuat. Saya tidak mematikan GlassFish server di NetBeans karena pada langkah berikutnya, saya akan memanggil web service ini di PHP.

Membuat Client Web Service dengan PHP

Sebelum mulai membuat client web service di PHP, saya memastikan apakah extension SOAP telah diaktifkan di PHP saya. Yang saya lakukan adalah membuat sebuah file PHP dengan nama info.php dimana isinya adalah <?php phpinfo(); ?>. Saat menampilkan halaman tersebut di browser, saya memastikan bahwa terdapat baris Soap Client dengan nilai Enabled.

Untuk membuat client web service, saya perlu membuat sebuah object dari class SoapClient seperti pada baris berikut ini:

$client = new SoapClient("http://localhost:8080/ServerWebService/PerhitunganWS?WSDL");

Nilai dari parameter constructur SoapClient adalah URL yang merujuk ke lokasi file WSDL. URL ini dapat dilihat di halaman web service tester pada saat saya menguji server web service di NetBeans.

Secara utuh, kode program PHP yang saya buat adalah:

<?php
function errorHandler($errno, $errstr, $errfile, $errline, array $errcontext) {
print "<h3>Terjadi kesalahan/peringatan:</h3>";
print "Baris $errline [$errstr]";
exit;
}

set_error_handler('errorHandler');

$client = new SoapClient("http://localhost:8080/ServerWebService/PerhitunganWS?WSDL");
$daftarOperasi = $client->__getFunctions();
print "<h3>Daftar Operasi Yang Tersedia Di Server WS:</h3>";
foreach ($daftarOperasi as $operasi) {
print "<p>$operasi</p>";
}
$hasil = $client->tambah(array('angka1'=>20, 'angka2'=>10));
print "<h3>Hasil operasi hitung(10,20): " . $hasil->return . "</h3>";
?>

<!--?php 

Pada kode program tersebut, saya menyertakan pemeriksaan kesalahan dengan set_error_handler(). Bagian tersebut dapat digantikan dengan menggunakan try/catch. Akan tetapi bila menggunakan try/catch, hanya pesan kesalahan yang ditampilkan, sementara pesan warning tidak akan ditampilkan. Bila pengaturan penampilan pesan kesalahan & warning secara otomatis tidak dimatikan (nilai display_errors di file konfigurasi adalah stdout), maka bagian ini tidak diperlukan.

Karena server web service di GlassFish menyediakan file WSDL, maka saya dapat menggunakan method __getFunctions() untuk melihat operasi apa saja yang disediakan oleh server web service tersebut.

Untuk memanggil salah satu operasi yang ada, saya cukup memanggil method dengan nama yang bersesuaian, diikuti dengan parameter yang diletakkan dalam associative array. Sebagai contoh, di kode program di atas, saya memanggil operasi tambah dengan nilai parameter angka1 berupa 20 dan nilai angka2 berupa 10. Hasil yang dikembalikan dari server web service adalah angka 30.