ฉันเริ่มต้นเว็บแอปพลิเคชันใหม่ใน PHP และคราวนี้ฉันต้องการสร้างสิ่งที่ผู้คนสามารถขยายได้โดยใช้ส่วนต่อประสานปลั๊กอิน
วิธีการหนึ่งที่เกี่ยวกับการเขียน 'hooks' ลงในรหัสของพวกเขาเพื่อให้ปลั๊กอินสามารถแนบกับเหตุการณ์ที่เฉพาะเจาะจงได้อย่างไร
ฉันเริ่มต้นเว็บแอปพลิเคชันใหม่ใน PHP และคราวนี้ฉันต้องการสร้างสิ่งที่ผู้คนสามารถขยายได้โดยใช้ส่วนต่อประสานปลั๊กอิน
วิธีการหนึ่งที่เกี่ยวกับการเขียน 'hooks' ลงในรหัสของพวกเขาเพื่อให้ปลั๊กอินสามารถแนบกับเหตุการณ์ที่เฉพาะเจาะจงได้อย่างไร
คำตอบ:
คุณสามารถใช้รูปแบบการสังเกตการณ์ วิธีการทำงานที่ง่ายเพื่อให้บรรลุสิ่งนี้:
<?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;
?>
เอาท์พุท:
This is my CRAZY application
4 + 5 = 9
4 * 5 = 20
หมายเหตุ:
สำหรับซอร์สโค้ดตัวอย่างนี้คุณต้องประกาศปลั๊กอินทั้งหมดก่อนซอร์สโค้ดจริงที่คุณต้องการขยายได้ ฉันได้รวมตัวอย่างวิธีจัดการค่าเดียวหรือหลายค่าที่ส่งผ่านไปยังปลั๊กอิน ส่วนที่ยากที่สุดคือการเขียนเอกสารจริงซึ่งแสดงรายการข้อโต้แย้งที่ส่งผ่านไปยังตะขอแต่ละอัน
นี่เป็นเพียงหนึ่งวิธีในการทำให้ระบบปลั๊กอินใน PHP ประสบความสำเร็จ มีทางเลือกที่ดีกว่าฉันขอแนะนำให้คุณตรวจสอบเอกสาร WordPress สำหรับข้อมูลเพิ่มเติม
Mediator Pattern
มันเป็นตัวอย่างของการที่ ผู้สังเกตการณ์ที่แท้จริงคือการแจ้งเตือนอย่างแท้จริงไม่มีการส่งข้อความหรือการแจ้งเตือนแบบมีเงื่อนไข (และไม่มีผู้จัดการกลางสำหรับการควบคุมการแจ้งเตือน) มันไม่ได้ทำให้คำตอบผิดแต่ควรสังเกตเพื่อหยุดคนเรียกสิ่งต่าง ๆ โดยใช้ชื่อที่ไม่ถูกต้อง ...
ดังนั้นสมมติว่าคุณไม่ต้องการรูปแบบการสังเกตเนื่องจากมันต้องการให้คุณเปลี่ยนวิธีการเรียนเพื่อจัดการงานการฟังและต้องการบางสิ่งบางอย่างทั่วไป และสมมติว่าคุณไม่ต้องการใช้การextends
สืบทอดเนื่องจากคุณอาจสืบทอดในคลาสของคุณจากคลาสอื่น มันจะไม่ดีที่จะมีวิธีการทั่วไปที่จะทำให้pluggable ระดับใด ๆ โดยไม่ต้องใช้ความพยายามมาก ? นี่คือวิธี:
<?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;
ในส่วนที่ 1 นั่นคือสิ่งที่คุณอาจรวมไว้กับการrequire_once()
โทรที่ด้านบนสุดของสคริปต์ PHP ของคุณ มันโหลดชั้นเรียนเพื่อให้บางสิ่งบางอย่างที่เสียบได้
ในส่วนที่ 2 นั่นคือที่ที่เราโหลดคลาส หมายเหตุฉันไม่ต้องทำอะไรเป็นพิเศษในชั้นเรียนซึ่งแตกต่างจากรูปแบบการสังเกตการณ์อย่างมีนัยสำคัญ
ในส่วนที่ 3 นั่นคือที่ที่เราสลับคลาสของเราให้กลายเป็น "เสียบได้" (นั่นคือรองรับปลั๊กอินที่ให้เราแทนที่เมธอดและคุณสมบัติของคลาส) ตัวอย่างเช่นหากคุณมีเว็บแอพคุณอาจมีปลั๊กอินรีจิสตรีและคุณสามารถเปิดใช้งานปลั๊กอินได้ที่นี่ แจ้งให้ทราบด้วยDog_bark_beforeEvent()
ฟังก์ชั่น หากฉันตั้งค่าไว้$mixed = 'BLOCK_EVENT'
ก่อนหน้าคำสั่งส่งคืนมันจะบล็อกสุนัขไม่ให้เห่าและจะบล็อก Dog_bark_afterEvent เพราะจะไม่มีเหตุการณ์ใด ๆ
ในส่วนที่ 4 นั่นคือรหัสการดำเนินการปกติ แต่สังเกตว่าสิ่งที่คุณอาจคิดว่าจะทำงานไม่ทำงานอย่างนั้น ตัวอย่างเช่นสุนัขไม่ได้ประกาศชื่อเป็น 'Fido' แต่เป็น 'Coco' สุนัขไม่ได้พูดว่า 'meow' แต่ 'Woof' และเมื่อคุณต้องการดูชื่อสุนัขหลังจากนั้นคุณจะพบว่า 'แตกต่าง' แทน 'Coco' การแทนที่ทั้งหมดนั้นมีให้ในส่วนที่ 3
แล้วมันทำงานอย่างไร ทีนี้มาแยกกันeval()
(ซึ่งทุกคนบอกว่าเป็น "ความชั่วร้าย") และแยกแยะว่ามันไม่ใช่รูปแบบผู้สังเกต ดังนั้นวิธีการทำงานก็คือคลาสที่ว่างเปล่าซึ่งเรียกว่า Pluggable ซึ่งไม่มีวิธีการและคุณสมบัติที่ใช้โดยคลาส Dog ดังนั้นตั้งแต่ที่เกิดขึ้นวิธีการที่วิเศษจะมีส่วนร่วมสำหรับเรา นั่นเป็นเหตุผลที่ในส่วนที่ 3 และ 4 เรายุ่งกับวัตถุที่ได้มาจากคลาส Pluggable ไม่ใช่คลาส Dog แต่เราปล่อยให้คลาสปลั๊กอินทำการ "แตะ" บนวัตถุ Dog แทนเรา (ถ้าเป็นรูปแบบการออกแบบที่ฉันไม่รู้ - กรุณาแจ้งให้เราทราบ)
เบ็ดและฟังเป็นวิธีการที่ใช้กันมากที่สุด แต่มีสิ่งอื่น ๆ ที่คุณสามารถทำได้ ขึ้นอยู่กับขนาดของแอพของคุณและใครที่คุณจะอนุญาตให้ดูรหัส (นี่จะเป็นสคริปต์ FOSS หรืออะไรบางอย่างในบ้าน) จะมีอิทธิพลอย่างมากต่อวิธีที่คุณต้องการอนุญาตปลั๊กอิน
kdeloach มีตัวอย่างที่ดี แต่การใช้งานและฟังก์ชั่น hook ของเขานั้นไม่ปลอดภัย ฉันจะขอให้คุณให้ข้อมูลเพิ่มเติมเกี่ยวกับลักษณะของ php app ที่คุณเขียนและวิธีที่คุณเห็นปลั๊กอินที่เหมาะสม
+1 ถึง kdeloach จากฉัน
นี่เป็นวิธีที่ฉันใช้มันเป็นความพยายามที่จะคัดลอกจากกลไกสัญญาณ / ช่องเสียบ Qt ซึ่งเป็นรูปแบบ Observer ชนิดหนึ่ง วัตถุสามารถส่งสัญญาณ สัญญาณทั้งหมดมี ID ในระบบ - มันประกอบด้วย id ของผู้ส่ง + ชื่อวัตถุทุกสัญญาณสามารถผูกกับตัวรับซึ่งก็คือ "callable" คุณใช้คลาสบัสเพื่อส่งสัญญาณไปยังใครก็ตามที่สนใจจะรับเมื่อมีบางอย่าง เกิดขึ้นคุณ "ส่ง" สัญญาณ ด้านล่างนี้คือตัวอย่างการใช้งาน
<?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();
?>
ฉันเชื่อว่าวิธีที่ง่ายที่สุดคือการทำตามคำแนะนำของ Jeff และดูรหัสที่มีอยู่ ลองดูที่ Wordpress, Drupal, Joomla และ CMS อื่น ๆ ที่เป็นที่รู้จักกันดีของ PHP เพื่อดูว่า API ของพวกเขามีลักษณะอย่างไร วิธีนี้คุณสามารถรับแนวคิดที่คุณอาจไม่เคยคิดมาก่อนเพื่อทำให้สิ่งต่าง ๆ เป็นสีแดงเข้มยิ่งขึ้น
คำตอบที่ตรงกว่าคือการเขียนไฟล์ทั่วไปที่พวกเขาจะ "รวมเข้าด้วยกัน" ลงในไฟล์ของพวกเขาที่จะให้การใช้งานที่พวกเขาต้องการ สิ่งนี้จะถูกแบ่งออกเป็นหมวดหมู่และไม่มีให้ในไฟล์ "hooks.php" ขนาดใหญ่หนึ่งไฟล์ ระวังเพราะสิ่งที่เกิดขึ้นคือไฟล์ที่พวกเขารวมถึงการมีการพึ่งพาและการทำงานที่ดีขึ้น พยายามทำให้ API อ้างอิงต่ำ IE ไฟล์ที่น้อยลงสำหรับพวกเขาที่จะรวม
มีโครงการที่ประณีตชื่อว่าSticklebackโดย Matt Zandstra ที่ Yahoo ซึ่งจัดการงานส่วนใหญ่สำหรับจัดการปลั๊กอินใน PHP
มันบังคับใช้อินเตอร์เฟซของชั้นปลั๊กอินสนับสนุนอินเตอร์เฟซบรรทัดคำสั่งและไม่ยากเกินไปที่จะได้รับและทำงาน - โดยเฉพาะอย่างยิ่งถ้าคุณอ่านหนังสือเรื่องเกี่ยวกับเรื่องนี้ในนิตยสารสถาปนิก PHP
คำแนะนำที่ดีคือดูว่าโครงการอื่น ๆ ได้ทำไปแล้ว หลายคนเรียกร้องให้ติดตั้งปลั๊กอินและลงทะเบียนชื่อ "บริการ" (เช่นเดียวกับ wordpress) ดังนั้นคุณจึงมี "คะแนน" ในรหัสของคุณที่คุณเรียกใช้ฟังก์ชันที่ระบุผู้ฟังที่ลงทะเบียนและดำเนินการ ลวดลายการออกแบบมาตรฐาน OO คือรูปแบบการสังเกตการณ์ซึ่งจะเป็นตัวเลือกที่ดีในการนำไปใช้ในระบบ PHP เชิงวัตถุอย่างแท้จริง
Zend Frameworkทำให้การใช้วิธีการ hooking จำนวนมากและมีการออกแบบอย่างมาก นั่นจะเป็นระบบที่ดีในการดู
ฉันประหลาดใจที่คำตอบส่วนใหญ่ที่นี่ดูเหมือนจะมุ่งเน้นเกี่ยวกับปลั๊กอินที่อยู่ในท้องถิ่นของแอปพลิเคชันเว็บเช่นปลั๊กอินที่ทำงานบนเว็บเซิร์ฟเวอร์ท้องถิ่น
ถ้าคุณต้องการให้ปลั๊กอินทำงานบนเซิร์ฟเวอร์ระยะไกลตัวอื่น วิธีที่ดีที่สุดในการทำเช่นนี้คือจัดเตรียมแบบฟอร์มที่ให้คุณกำหนด URL ที่แตกต่างกันซึ่งจะถูกเรียกเมื่อมีเหตุการณ์เฉพาะเกิดขึ้นในแอปพลิเคชันของคุณ
เหตุการณ์ต่าง ๆ จะส่งข้อมูลที่แตกต่างกันไปตามเหตุการณ์ที่เพิ่งเกิดขึ้น
ด้วยวิธีนี้คุณจะทำการเรียก cURL ไปยัง URL ที่ให้ไว้ในแอปพลิเคชันของคุณ (เช่นผ่าน https) ซึ่งเซิร์ฟเวอร์ระยะไกลสามารถทำงานตามข้อมูลที่แอปพลิเคชันของคุณส่งมา
สิ่งนี้มีสองประโยชน์: