Saya memulai aplikasi web baru dalam PHP dan kali ini saya ingin membuat sesuatu yang dapat diperluas orang dengan menggunakan antarmuka plugin.
Bagaimana cara menulis 'kait' ke dalam kode mereka sehingga plugin dapat melampirkan ke acara tertentu?
Saya memulai aplikasi web baru dalam PHP dan kali ini saya ingin membuat sesuatu yang dapat diperluas orang dengan menggunakan antarmuka plugin.
Bagaimana cara menulis 'kait' ke dalam kode mereka sehingga plugin dapat melampirkan ke acara tertentu?
Jawaban:
Anda bisa menggunakan pola Observer. Cara fungsional sederhana untuk mencapai ini:
<?php
/** Plugin system **/
$listeners = array();
/* Create an entry point for plugins */
function hook() {
global $listeners;
$num_args = func_num_args();
$args = func_get_args();
if($num_args < 2)
trigger_error("Insufficient arguments", E_USER_ERROR);
// Hook name should always be first argument
$hook_name = array_shift($args);
if(!isset($listeners[$hook_name]))
return; // No plugins have registered this hook
foreach($listeners[$hook_name] as $func) {
$args = $func($args);
}
return $args;
}
/* Attach a function to a hook */
function add_listener($hook, $function_name) {
global $listeners;
$listeners[$hook][] = $function_name;
}
/////////////////////////
/** Sample Plugin **/
add_listener('a_b', 'my_plugin_func1');
add_listener('str', 'my_plugin_func2');
function my_plugin_func1($args) {
return array(4, 5);
}
function my_plugin_func2($args) {
return str_replace('sample', 'CRAZY', $args[0]);
}
/////////////////////////
/** Sample Application **/
$a = 1;
$b = 2;
list($a, $b) = hook('a_b', $a, $b);
$str = "This is my sample application\n";
$str .= "$a + $b = ".($a+$b)."\n";
$str .= "$a * $b = ".($a*$b)."\n";
$str = hook('str', $str);
echo $str;
?>
Keluaran:
This is my CRAZY application
4 + 5 = 9
4 * 5 = 20
Catatan:
Untuk contoh kode sumber ini, Anda harus mendeklarasikan semua plugin Anda sebelum kode sumber aktual yang Anda inginkan dapat diperpanjang. Saya telah menyertakan contoh bagaimana menangani nilai tunggal atau ganda yang diteruskan ke plugin. Bagian tersulit dari ini adalah menulis dokumentasi aktual yang mencantumkan argumen apa yang diteruskan ke setiap kait.
Ini hanyalah salah satu metode untuk menyelesaikan sistem plugin dalam PHP. Ada alternatif yang lebih baik, saya sarankan Anda memeriksa Dokumentasi WordPress untuk informasi lebih lanjut.
Mediator Pattern
. Pengamat sejati adalah pemberitahuan murni, tidak ada pesan yang lewat atau pemberitahuan bersyarat (juga tidak ada manajer pusat untuk mengendalikan pemberitahuan). Itu tidak membuat jawaban salah , tetapi harus dicatat untuk menghentikan orang memanggil sesuatu dengan nama yang salah ...
Jadi katakanlah Anda tidak menginginkan pola Observer karena mengharuskan Anda mengubah metode kelas Anda untuk menangani tugas mendengarkan, dan menginginkan sesuatu yang generik. Dan katakanlah Anda tidak ingin menggunakannyaextends
warisan karena Anda mungkin sudah mewarisi di kelas Anda dari beberapa kelas lain. Bukankah lebih baik memiliki cara generik untuk membuat kelas mana pun dapat dicolokkan tanpa banyak usaha ? Begini caranya:
<?php
////////////////////
// PART 1
////////////////////
class Plugin {
private $_RefObject;
private $_Class = '';
public function __construct(&$RefObject) {
$this->_Class = get_class(&$RefObject);
$this->_RefObject = $RefObject;
}
public function __set($sProperty,$mixed) {
$sPlugin = $this->_Class . '_' . $sProperty . '_setEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
$this->_RefObject->$sProperty = $mixed;
}
public function __get($sProperty) {
$asItems = (array) $this->_RefObject;
$mixed = $asItems[$sProperty];
$sPlugin = $this->_Class . '_' . $sProperty . '_getEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
return $mixed;
}
public function __call($sMethod,$mixed) {
$sPlugin = $this->_Class . '_' . $sMethod . '_beforeEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
if ($mixed != 'BLOCK_EVENT') {
call_user_func_array(array(&$this->_RefObject, $sMethod), $mixed);
$sPlugin = $this->_Class . '_' . $sMethod . '_afterEvent';
if (is_callable($sPlugin)) {
call_user_func_array($sPlugin, $mixed);
}
}
}
} //end class Plugin
class Pluggable extends Plugin {
} //end class Pluggable
////////////////////
// PART 2
////////////////////
class Dog {
public $Name = '';
public function bark(&$sHow) {
echo "$sHow<br />\n";
}
public function sayName() {
echo "<br />\nMy Name is: " . $this->Name . "<br />\n";
}
} //end class Dog
$Dog = new Dog();
////////////////////
// PART 3
////////////////////
$PDog = new Pluggable($Dog);
function Dog_bark_beforeEvent(&$mixed) {
$mixed = 'Woof'; // Override saying 'meow' with 'Woof'
//$mixed = 'BLOCK_EVENT'; // if you want to block the event
return $mixed;
}
function Dog_bark_afterEvent(&$mixed) {
echo $mixed; // show the override
}
function Dog_Name_setEvent(&$mixed) {
$mixed = 'Coco'; // override 'Fido' with 'Coco'
return $mixed;
}
function Dog_Name_getEvent(&$mixed) {
$mixed = 'Different'; // override 'Coco' with 'Different'
return $mixed;
}
////////////////////
// PART 4
////////////////////
$PDog->Name = 'Fido';
$PDog->Bark('meow');
$PDog->SayName();
echo 'My New Name is: ' . $PDog->Name;
Di Bagian 1, itulah yang mungkin Anda sertakan dengan a require_once()
panggilan di bagian atas skrip PHP Anda. Ini memuat kelas-kelas untuk membuat sesuatu yang dapat dicolokkan
Di Bagian 2, di situlah kita memuat kelas. Catatan saya tidak perlu melakukan sesuatu yang khusus untuk kelas, yang secara signifikan berbeda dari pola Pengamat.
Di Bagian 3, di situlah kita mengubah kelas kita menjadi "pluggable" (artinya, mendukung plugin yang memungkinkan kita mengganti metode dan properti kelas). Jadi, misalnya, jika Anda memiliki aplikasi web, Anda mungkin memiliki registry plugin, dan Anda dapat mengaktifkan plugin di sini. Perhatikan jugaDog_bark_beforeEvent()
fungsinya. Jika saya mengatur $mixed = 'BLOCK_EVENT'
sebelum pernyataan kembali, itu akan memblokir anjing dari menggonggong dan juga akan memblokir Dog_bark_afterEvent karena tidak akan ada acara.
Di Bagian 4, itulah kode operasi normal, tetapi perhatikan bahwa apa yang Anda pikir akan berjalan tidak berjalan sama sekali. Misalnya, anjing tidak mengumumkan nama itu sebagai 'Fido', tetapi 'Coco'. Anjing itu tidak mengatakan 'meow', tetapi 'Woof'. Dan ketika Anda ingin melihat nama anjing setelahnya, Anda menemukan itu 'Berbeda' dan bukan 'Coco'. Semua penggantian tersebut disediakan di Bagian 3.
Jadi bagaimana cara kerjanya? Baiklah, mari kita singkirkan eval()
(yang semua orang katakan adalah "jahat") dan singkirkan bahwa itu bukan pola Pengamat. Jadi, cara kerjanya adalah kelas kosong licik yang disebut Pluggable, yang tidak mengandung metode dan properti yang digunakan oleh kelas Dog. Jadi, sejak itu terjadi, metode sihir akan melibatkan kita. Itu sebabnya di bagian 3 dan 4 kita mengacaukan objek yang berasal dari kelas Pluggable, bukan kelas Dog itu sendiri. Sebagai gantinya, kami membiarkan kelas Plugin melakukan "sentuhan" pada objek Dog untuk kami. (Jika itu semacam pola desain yang tidak saya ketahui - tolong beri tahu saya.)
The kait dan pendengar adalah yang paling umum digunakan, tetapi ada hal-hal lain yang dapat Anda lakukan. Bergantung pada ukuran aplikasi Anda, dan siapa yang akan Anda izinkan melihat kode (apakah ini akan menjadi skrip FOSS, atau sesuatu di dalam rumah) akan sangat memengaruhi cara Anda ingin mengizinkan plugin.
kdeloach memiliki contoh yang bagus, tetapi fungsi implement dan hook-nya sedikit tidak aman. Saya akan meminta Anda untuk memberikan informasi lebih lanjut tentang sifat dari aplikasi php tulisan Anda, Dan bagaimana Anda melihat plugin masuk
+1 ke kdeloach dari saya.
Ini adalah pendekatan yang saya gunakan, ini merupakan upaya untuk menyalin dari mekanisme sinyal / slot Qt, semacam pola Observer. Objek dapat memancarkan sinyal. Setiap sinyal memiliki ID dalam sistem - itu disusun oleh id + nama objek pengirim. Setiap sinyal dapat diikatkan ke penerima, yang hanya merupakan "panggilan". Anda menggunakan kelas bus untuk menyampaikan sinyal kepada siapa saja yang tertarik menerimanya. Ketika sesuatu terjadi, Anda "mengirim" sinyal. Di bawah ini adalah dan contoh implementasi
<?php
class SignalsHandler {
/**
* hash of senders/signals to slots
*
* @var array
*/
private static $connections = array();
/**
* current sender
*
* @var class|object
*/
private static $sender;
/**
* connects an object/signal with a slot
*
* @param class|object $sender
* @param string $signal
* @param callable $slot
*/
public static function connect($sender, $signal, $slot) {
if (is_object($sender)) {
self::$connections[spl_object_hash($sender)][$signal][] = $slot;
}
else {
self::$connections[md5($sender)][$signal][] = $slot;
}
}
/**
* sends a signal, so all connected slots are called
*
* @param class|object $sender
* @param string $signal
* @param array $params
*/
public static function signal($sender, $signal, $params = array()) {
self::$sender = $sender;
if (is_object($sender)) {
if ( ! isset(self::$connections[spl_object_hash($sender)][$signal])) {
return;
}
foreach (self::$connections[spl_object_hash($sender)][$signal] as $slot) {
call_user_func_array($slot, (array)$params);
}
}
else {
if ( ! isset(self::$connections[md5($sender)][$signal])) {
return;
}
foreach (self::$connections[md5($sender)][$signal] as $slot) {
call_user_func_array($slot, (array)$params);
}
}
self::$sender = null;
}
/**
* returns a current signal sender
*
* @return class|object
*/
public static function sender() {
return self::$sender;
}
}
class User {
public function login() {
/**
* try to login
*/
if ( ! $logged ) {
SignalsHandler::signal(this, 'loginFailed', 'login failed - username not valid' );
}
}
}
class App {
public static function onFailedLogin($message) {
print $message;
}
}
$user = new User();
SignalsHandler::connect($user, 'loginFailed', array($Log, 'writeLog'));
SignalsHandler::connect($user, 'loginFailed', array('App', 'onFailedLogin'));
$user->login();
?>
Saya percaya cara termudah adalah mengikuti saran Jeff sendiri dan melihat-lihat kode yang ada. Coba lihat Wordpress, Drupal, Joomla dan CMS berbasis PHP terkenal lainnya untuk melihat bagaimana kait dan tampilan API mereka. Dengan cara ini Anda bahkan bisa mendapatkan ide-ide yang mungkin belum terpikirkan sebelumnya untuk membuat segalanya menjadi lebih banyak sampah.
Jawaban yang lebih langsung adalah menulis file-file umum yang akan mereka "sertakan" pada file mereka yang akan memberikan kegunaan yang mereka butuhkan. Ini akan dipecah menjadi beberapa kategori dan TIDAK disediakan dalam satu file "hooks.php" BESAR. Berhati-hatilah, karena apa yang akhirnya terjadi adalah bahwa file yang mereka masukkan akhirnya memiliki lebih banyak ketergantungan dan fungsionalitas yang meningkat. Usahakan agar ketergantungan API tetap rendah. IE lebih sedikit file untuk mereka sertakan.
Ada proyek rapi bernama Stickleback oleh Matt Zandstra di Yahoo yang menangani banyak pekerjaan untuk menangani plugin dalam PHP.
Ini menegakkan antarmuka kelas plugin, mendukung antarmuka baris perintah dan tidak terlalu sulit untuk bangun dan berjalan - terutama jika Anda membaca cerita sampul tentang hal itu di majalah arsitek PHP .
Saran yang baik adalah untuk melihat bagaimana proyek lain melakukannya. Banyak panggilan untuk memasang plugin dan "nama" mereka terdaftar untuk layanan (seperti halnya wordpress) sehingga Anda memiliki "poin" dalam kode Anda tempat Anda memanggil fungsi yang mengidentifikasi pendengar terdaftar dan mengeksekusinya. Pola desain OO standar adalah Pola Pengamat , yang akan menjadi pilihan yang baik untuk diterapkan dalam sistem PHP yang benar-benar berorientasi objek.
The Zend Framework memanfaatkan banyak metode hooking, dan sangat baik architected. Itu akan menjadi sistem yang bagus untuk dilihat.
Saya terkejut bahwa sebagian besar jawaban di sini tampaknya diarahkan tentang plugin yang bersifat lokal untuk aplikasi web, yaitu, plugin yang berjalan di server web lokal.
Bagaimana jika Anda ingin plugin dijalankan di server yang berbeda? Cara terbaik untuk melakukan ini adalah dengan menyediakan formulir yang memungkinkan Anda untuk menentukan berbagai URL yang akan dipanggil ketika peristiwa tertentu terjadi dalam aplikasi Anda.
Peristiwa yang berbeda akan mengirimkan informasi yang berbeda berdasarkan peristiwa yang baru saja terjadi.
Dengan cara ini, Anda hanya akan melakukan panggilan CURL ke URL yang telah disediakan untuk aplikasi Anda (misalnya lebih dari https) di mana server jauh dapat melakukan tugas berdasarkan informasi yang telah dikirim oleh aplikasi Anda.
Ini memberikan dua manfaat: