Bir PHP uygulaması için eklentilere izin vermenin en iyi yolu


276

PHP'de yeni bir web uygulaması başlatıyorum ve bu sefer insanların bir eklenti arayüzü kullanarak genişletmek bir şey oluşturmak istiyorum.

Eklentilerin belirli olaylara eklenebilmesi için kodlarına "kancalar" yazma hakkında nasıl bilgi verilir?

Yanıtlar:


162

Bir Gözlemci deseni kullanabilirsiniz. Bunu başarmanın basit bir fonksiyonel yolu:

<?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;
?>

Çıktı:

This is my CRAZY application
4 + 5 = 9
4 * 5 = 20

Notlar:

Bu örnek kaynak kodu için, genişletilebilir olmasını istediğiniz gerçek kaynak kodundan önce tüm eklentilerinizi bildirmeniz gerekir. Eklentiye iletilen tek veya birden çok değeri nasıl ele alacağınıza dair bir örnek ekledim. Bunun en zor yanı, her bir kancaya hangi argümanların aktarıldığını listeleyen asıl belgeleri yazmaktır.

Bu PHP bir eklenti sistemi elde etmek için sadece bir yöntemdir. Daha iyi alternatifler var, daha fazla bilgi için WordPress Belgelerine göz atmanızı öneririz.


3
PHP> = 5.0 için bunu SPL'de
John Carter

20
Bilgiçliksel not: Bu, Gözlemci modeline bir örnek değildir. Bunun bir örneği Mediator Pattern. Gerçek gözlemciler tamamen bildirimdir, mesaj iletimi veya koşullu bildirim yoktur (bildirimleri kontrol etmek için merkezi bir yönetici de yoktur). Cevabı yanlış yapmaz , ancak insanların yanlış adla bir şeyler çağırmasını durdurduğuna dikkat edilmelidir ...
ircmaxell

Birden fazla kanca / dinleyici kullanırken, her ikisini birden değil, yalnızca dizeleri veya dizileri döndürmeniz gerektiğini unutmayın. Hound CMS için benzer bir şey uyguladım - getbutterfly.com/hound .
Ciprian

59

Diyelim ki Gözlemci kalıbı istemiyorsunuz, çünkü dinleme görevini yerine getirmek ve genel bir şey istemek için sınıf yöntemlerinizi değiştirmeniz gerekiyor. Ve diyelim ki extendsmiras kullanmak istemiyorsunuz çünkü sınıfınızda zaten başka bir sınıftan miras alıyor olabilirsiniz. Herhangi bir sınıfı fazla çaba sarf etmeden takılabilir yapmak için genel bir yol olması harika olmaz mıydı ? Bunu nasıl yapacağınız aşağıda açıklanmıştır:

<?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;

Bölüm 1'de, require_once()PHP betiğinizin üst kısmındaki bir çağrıya dahil edebileceğiniz şey budur . Takılabilir bir şey yapmak için sınıfları yükler.

Bölüm 2'de, burada bir sınıf yüklüyoruz. Not Gözlemci modelden önemli ölçüde farklı olan sınıfa özel bir şey yapmak zorunda değildim.

Bölüm 3'te, burada sınıfımızı "takılabilir" olarak değiştiriyoruz (yani, sınıf yöntemlerini ve özelliklerini geçersiz kılmamızı sağlayan eklentileri destekler). Örneğin, bir web uygulamanız varsa, bir eklenti kayıt defteriniz olabilir ve burada eklentileri etkinleştirebilirsiniz. Ayrıca Dog_bark_beforeEvent()fonksiyona dikkat edin . $mixed = 'BLOCK_EVENT'Dönüş ifadesinden önce ayarladıysam , köpeğin havlamasını engelleyecek ve ayrıca herhangi bir olay olmayacağı için Dog_bark_afterEvent'i de engelleyecektir.

Bölüm 4'te, bu normal işlem kodu, ancak çalışacağını düşündüğünüz şeyin hiç böyle çalışmadığına dikkat edin. Örneğin, köpek adını 'Fido' olarak değil, 'Coco' olarak ilan eder. Köpek 'miyav' demez, 'Woof' der. Daha sonra köpeğin ismine bakmak istediğinizde, 'Coco' yerine 'Farklı' olduğunu görürsünüz. Bütün bu geçersiz kılmalar Bölüm 3'te verilmiştir.

Peki bu nasıl çalışıyor? Peki, hadi dışarı çıkalım eval()(ki herkes "kötülük" der) ve bir Gözlemci kalıbı olmadığına karar verelim . Yani, çalışma şekli, Köpek sınıfı tarafından kullanılan yöntemleri ve özellikleri içermeyen Pluggable adlı gizli sinsi sınıftır. Böylece, bu gerçekleştiğinden beri, sihirli yöntemler bizim için meşgul olacak. Bu nedenle 3. ve 4. bölümlerde, Köpek sınıfının kendisi değil, Pluggable sınıfından türetilen nesneyle uğraşıyoruz. Bunun yerine, Plugin sınıfının bizim için Dog nesnesine "dokunma" yapmasına izin veriyoruz. (Bu bilmediğim bir tür tasarım deseni ise - lütfen bana bildirin.)


3
Bu bir dekoratör değil mi?
MV.

1
Ben bu konuda Wikipedia'da okumak dur, haklısın, ve! :)
Volomike

35

Kanca ve dinleyici yöntemi yaygın olarak kullanılan çoğu, ancak yapabileceğimiz başka şeyler vardır. Uygulamanızın boyutuna ve kodu kime izin vereceğinize bağlı olarak (bu bir FOSS komut dosyası mı yoksa evde bir şey mi olacak) eklentilere nasıl izin vermek istediğinizi büyük ölçüde etkileyecektir.

kdeloach'un güzel bir örneği var, ancak uygulaması ve kanca işlevi biraz güvensiz. Sizden php uygulamasının doğası hakkında daha fazla bilgi vermenizi ve yazınızı nasıl eklediğinizi görmenizi rica ediyorum.

+1 benden kdeloach.


25

İşte kullandığım bir yaklaşım, Qt sinyalleri / yuvaları mekanizmasından, bir çeşit Observer paterninden kopyalama girişimidir. Nesneler sinyal yayabilir. Her sinyalin sistemde bir kimliği vardır - gönderenin kimliği + nesne adından oluşur Her sinyal alıcılara bağlanabilir, bu sadece "çağrılabilir" bir sinyalden almak için bir veri yolu sınıfı kullanırsınız. bir sinyal gönderirsiniz. Aşağıda ve örnek uygulama

    <?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();

?>

18

En kolay yolun Jeff'in tavsiyelerine uymak ve mevcut koda bir göz atmak olduğuna inanıyorum. API kancalarının nasıl göründüğünü ve hissettiğini görmek için Wordpress, Drupal, Joomla ve diğer bilinen PHP tabanlı CMS'lere bakmayı deneyin. Bu şekilde, işleri biraz daha kaba yapmak için daha önce düşünmediğiniz fikirleri bile alabilirsiniz.

Daha doğrudan bir cevap, dosyalarına "include_once" yapacakları genel dosyaları, ihtiyaç duydukları kullanılabilirliği sağlayacak yazmaktır. Bu kategorilere ayrılır ve bir MASSIVE "hooks.php" dosyasında VERİLMEZ. Ancak dikkatli olun, çünkü ortaya çıkan şey, içerdikleri dosyaların gittikçe daha fazla bağımlılığa ve işlevselliğe sahip olmasıdır. API bağımlılıklarını düşük tutmaya çalışın. IE onlar için daha az dosya dahil etmek.


Bakabileceğiniz sistemler listesine DokuWiki'yi eklerdim. Zengin bir eklenti ekosistemine izin veren güzel bir etkinlik sistemine sahiptir.
chiborg

15

Yahoo'da Matt Zandstra tarafından Stickleback adı verilen ve PHP'de eklentileri ele alma işlerinin çoğunu üstlenen düzgün bir proje var .

Bir eklenti sınıfının arabirimini zorlar, bir komut satırı arabirimini destekler ve kalkmak için çok zor değildir - özellikle PHP mimar dergisinde bununla ilgili kapak hikayesini okursanız .


11

İyi tavsiye, diğer projelerin bunu nasıl yaptığına bakmaktır. Birçoğu, eklentilerin kurulu olmasını ve hizmetlerinin "wordpress yaptığı gibi" kayıtlı olduklarını belirtir; böylece kodunuzda, kayıtlı dinleyicileri tanımlayan ve yürüten bir işlevi çağırdığınız "noktalar" bulunur. Standart bir OO tasarım patenti, gerçekten nesne yönelimli bir PHP sisteminde uygulamak için iyi bir seçenek olabilecek Gözlemci Deseni'dir .

Zend Framework birçok çengel yöntemler kullanmaktadır ve çok güzel architected edilir. Bakmak için iyi bir sistem olurdu.


8

Buradaki yanıtların çoğunun, web uygulamasında yerel olan eklentiler, yani yerel web sunucusunda çalışan eklentiler hakkında olduğu düşünülüyor.

Eklentilerin farklı bir uzak sunucuda çalışmasını isterseniz ne olur? Bunu yapmanın en iyi yolu, uygulamanızda belirli olaylar gerçekleştiğinde çağrılacak farklı URL'leri tanımlamanıza izin veren bir form sağlamaktır.

Farklı etkinlikler, gerçekleşen olaya bağlı olarak farklı bilgiler gönderir.

Bu şekilde, uzak sunucuların uygulamanız tarafından gönderilen bilgilere dayanarak görevleri yerine getirebileceği, uygulamanıza sağlanan URL'ye (ör. Https üzerinden) bir cURL çağrısı gerçekleştirirsiniz.

Bu iki fayda sağlar:

  1. Yerel sunucunuzda herhangi bir kod barındırmanız gerekmez (güvenlik)
  2. Kod, PHP (taşınabilirlik) dışındaki farklı dillerde uzak sunucularda (genişletilebilirlik) olabilir

8
Bu, bir "eklenti" sisteminden daha çok "push API" dır - diğer hizmetlerin seçilen etkinliklerle ilgili bildirim alması için bir yol sağlıyorsunuz. Genellikle "eklentiler" ile kastedilen, uygulamayı yükleyebilmeniz ve daha sonra davranışını amaçlarınıza göre özelleştirmek için işlevsellik ekleyebilmenizdir, bu da eklentinin yerel olarak çalışmasını gerektirir - veya en azından sağlamak için güvenli ve etkili bir 2 yönlü iletişim bilgi için uygulamanın sadece alamaz dan o. İki özellik biraz farklıdır ve birçok durumda bir "feed" (ör. RSS, iCal) push API'sine basit bir alternatiftir.
IMSoP
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.