PHP 응용 프로그램에 플러그인을 허용하는 가장 좋은 방법


276

PHP로 새로운 웹 응용 프로그램을 시작하고 있으며 이번에는 플러그인 인터페이스를 사용하여 사람들이 확장 할 수있는 것을 만들고 싶습니다.

플러그인이 특정 이벤트에 연결할 수 있도록 코드에 '후크'를 작성하는 방법은 무엇입니까?

답변:


162

관찰자 패턴을 사용할 수 있습니다. 이것을 달성하는 간단한 기능적 방법 :

<?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 설명서를 참조하십시오.


3
PHP> = 5.0의 경우 SPL에 정의 된 Observer / Subject 인터페이스를 사용하여이를 구현할 수 있습니다. php.net/manual/en/class.splobserver.php
John Carter

20
Pedantic 참고 : 이것은 관찰자 패턴의 예가 아닙니다. 의 예입니다 Mediator Pattern. 진정한 관찰자는 순전히 알림이며, 메시지 전달이나 조건부 알림이 없습니다 (알림 제어를위한 중앙 관리자도 없음). 대답이 틀리지 는 않지만 사람들이 잘못된 이름으로 전화를
걸지

여러 후크 / 리스너를 사용하는 경우 문자열이나 배열 만 반환해야하며 둘 다 반환하지 않아야합니다. Hound CMS-getbutterfly.com/hound 와 비슷한 것을 구현 했습니다 .
Ciprian

59

청취자의 작업을 처리하기 위해 클래스 메소드를 변경하고 일반적인 것을 원하기 때문에 Observer 패턴을 원하지 않는다고 가정 해 봅시다. 그리고 extends클래스에서 이미 다른 클래스의 상속을 받았기 때문에 상속 을 사용하고 싶지 않다고 가정 해 봅시다 . 많은 노력없이 클래스를 플러그 가능 하게 만드는 일반적인 방법을 사용하는 것이 좋지 않습니까? 방법은 다음과 같습니다.

<?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 스크립트 맨 위에 호출에 . 플러그 인 가능하게 만들기 위해 클래스를로드합니다.

Part 2에서는 클래스를로드합니다. 참고 클래스에 특별한 작업을 수행 할 필요가 없었으며 이는 관찰자 패턴과 크게 다릅니다.

Part 3에서는 클래스를 "플러그 가능"으로 바꾸는 곳이다 (즉, 클래스 메소드와 속성을 재정의 할 수있는 플러그인을 지원한다). 예를 들어, 웹 앱이있는 경우 플러그인 레지스트리가있을 수 있으며 여기에서 플러그인을 활성화 할 수 있습니다. Dog_bark_beforeEvent()기능 도 주목하십시오 . $mixed = 'BLOCK_EVENT'return 문 전에 설정 하면 개가 짖는 것을 차단하고 이벤트가 없기 때문에 Dog_bark_afterEvent도 차단합니다.

Part 4에서는 이것이 일반적인 작업 코드이지만 실행한다고 생각되는 것이 전혀 실행되지 않는다는 점에 주목하십시오. 예를 들어, 개는 이름을 'Fido'가 아니라 'Coco'라고 발표하지 않습니다. 개는 'meow'가 아니라 'Woof'라고 말합니다. 그리고 나중에 강아지의 이름을보고 싶을 때 'Coco'대신 'Different'라는 것을 알 수 있습니다. 이러한 모든 재정의는 3 부에서 제공되었습니다.

어떻게 작동합니까? 자, eval()모두가 "악"이라고 말하지 말고 관찰자 패턴이 아니라고 배제하자. 따라서 작동 방식은 Dog 클래스에서 사용하는 메서드 및 속성을 포함하지 않는 Pluggable이라는 비열한 빈 클래스입니다. 따라서 그것이 발생하기 때문에 마법의 방법이 우리를 참여시킬 것입니다. 그렇기 때문에 3 부와 4 부에서 Dog 클래스 자체가 아니라 Pluggable 클래스에서 파생 된 객체를 엉망으로 만듭니다. 대신, Plugin 클래스가 Dog 객체에서 "터칭"을 수행하도록합니다. (그것이 내가 모르는 일종의 디자인 패턴이라면 알려주십시오.)


3
이것은 데코레이터가 아닙니까?
MV.

1
나는 이것에 대해 Wikipedia를 읽었고 , 당신은 맞습니다! :)
Volomike

35

후크리스너 방법은 일반적으로 사용되는 대부분이지만, 당신이 할 수있는 다른 일이있다. 앱의 크기와 코드를 볼 수있는 사람 (FOSS 스크립트 또는 사내 무언가)에 따라 플러그인 허용 방법에 큰 영향을 미칩니다.

kdeloach는 좋은 예를 가지고 있지만 그의 구현 및 후크 기능은 약간 안전하지 않습니다. PHP 응용 프로그램의 특성에 대한 자세한 정보와 글을 작성하는 방법 및 플러그인이 표시되는 방법을 알려달라고 부탁합니다.

나에게서 kdeloach하는 +1


25

여기 내가 사용한 접근법이 있는데, 그것은 일종의 관찰자 패턴 인 Qt 신호 / 슬롯 메커니즘에서 복사하려는 시도입니다. 물체는 신호를 방출 할 수 있습니다. 모든 신호는 시스템에 ID가 있습니다-발신자의 id + 객체 이름으로 구성됩니다. 모든 신호는 수신자에게 바인딩 될 수 있습니다. 단순히 "호출 가능"입니다. 버스 클래스를 사용하여 신호 수신에 관심이있는 사람에게 신호를 전달합니다 발생, 당신은 신호를 "보냅니다". 아래는 예제 구현입니다.

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

가장 쉬운 방법은 Jeff의 조언을 따르고 기존 코드를 살펴 보는 것입니다. APIpress의 모양과 느낌을 보려면 Wordpress, Drupal, Joomla 및 기타 유명한 PHP 기반 CMS를 살펴보십시오. 이렇게하면 이전에 생각하지 않았을 수도있는 아이디어를 얻을 수 있습니다.

보다 직접적인 대답은 필요한 사용성을 제공 할 파일에 "include_once"인 일반 파일을 작성하는 것입니다. 이것은 카테고리로 분류되며 하나의 대규모 "hooks.php"파일에는 제공되지 않습니다. 그러나 결국에는 파일에 포함되는 파일이 점점 더 많은 종속성과 기능을 갖기 때문에주의해야합니다. API 종속성을 낮게 유지하십시오. 포함 할 파일 수가 적습니다.


DokuWiki를 당신이 살펴볼 시스템 목록에 추가하고 싶습니다. 풍부한 플러그인 생태계를 허용하는 멋진 이벤트 시스템이 있습니다.
chiborg

15

Stickleback 이라는 깔끔한 프로젝트가 있습니다Yahoo의 Matt Zandstra에 의해 , PHP에서 플러그인을 다루기위한 많은 작업을 처리합니다.

플러그인 클래스의 인터페이스를 시행하고 명령 행 인터페이스를 지원하며 시작하기가 너무 어렵지 않습니다. 특히 PHP 아키텍트 잡지 에서 이에 대한 기사를 읽으면 더욱 그렇습니다 .


11

다른 프로젝트가 어떻게했는지 살펴 보는 것이 좋습니다. 많은 사람들이 플러그인을 설치하고 서비스에 "이름"을 등록해야하므로 (예 : 워드 프레스처럼) 등록 된 리스너를 식별하고 실행하는 함수를 호출하는 코드에 "포인트"가 있습니다. 표준 OO 디자인 패턴은 Observer Pattern 이며, 이는 객체 지향 PHP 시스템에서 구현하기에 좋은 옵션입니다.

젠드 프레임 워크는 많은 후킹 방법을 사용한다, 매우 잘 설계됐다. 보기 좋은 시스템이 될 것입니다.


8

여기에있는 대부분의 답변이 웹 응용 프로그램에 로컬 인 플러그인, 즉 로컬 웹 서버에서 실행되는 플러그인에 관한 것 같습니다.

플러그인을 다른 원격 서버에서 실행하려면 어떻게해야합니까? 이를 수행하는 가장 좋은 방법은 애플리케이션에서 특정 이벤트가 발생할 때 호출되는 다른 URL을 정의 할 수있는 양식을 제공하는 것입니다.

다른 이벤트는 방금 발생한 이벤트에 따라 다른 정보를 보냅니다.

이렇게하면 원격 서버가 응용 프로그램에서 보낸 정보를 기반으로 작업을 수행 할 수있는 응용 프로그램에 제공된 URL (예 : https를 통해)에 대한 cURL 호출을 수행 할 수 있습니다.

두 가지 이점이 있습니다.

  1. 로컬 서버에서 코드를 호스팅 할 필요가 없습니다 (보안)
  2. 코드는 PHP (이동성) 이외의 다른 언어로 원격 서버 (확장 성)에있을 수 있습니다.

8
이것은 "플러그인"시스템보다 "푸시 API"에 가깝습니다. 다른 서비스가 선택된 이벤트에 대한 알림을받을 수있는 방법을 제공하고 있습니다. 일반적으로 "플러그인"의 의미는 응용 프로그램을 설치 한 다음 기능을 사용자의 목적에 맞게 사용자 지정할 수있는 기능을 추가 할 수 있다는 것입니다. 플러그인은 로컬에서 실행되어야합니다. 정보 에 대한 응용 프로그램은 단지 그것을하지 에서 그것. 두 기능은 다소 구별되며, 많은 경우 "피드"(예 : RSS, iCal)는 푸시 API의 간단한 대안입니다.
IMSoP
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.