문제

저는 PHP로 새로운 웹 애플리케이션을 시작하고 있는데 이번에는 사람들이 플러그인 인터페이스를 사용하여 확장할 수 있는 것을 만들고 싶습니다.

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

도움이 되었습니까?

해결책

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

<?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 문서를 확인하는 것이 좋습니다.

죄송합니다. 마크다운에 의해 밑줄 문자가 HTML 엔터티로 대체된 것 같습니다.이 버그가 수정되면 이 코드를 다시 게시할 수 있습니다.

편집하다:신경쓰지 마세요. 편집할 때만 그런 식으로 나타납니다.

다른 팁

따라서 청취 작업을 처리하기 위해 클래스 메서드를 변경해야 하고 일반적인 것을 원하기 때문에 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'라고 말합니다.개는 '야옹'이라고 말하지 않고 '멍' 소리를 냅니다.그리고 나중에 강아지 이름을 찾아보면 '코코'가 아닌 '디퍼런트'라는 것을 알 수 있습니다.이러한 재정의는 모두 3부에서 제공되었습니다.

그러면 이것이 어떻게 작동하나요?글쎄, 배제하자 eval() (모두가 "악"이라고 말합니다) 관찰자 패턴이 아니라는 점을 배제하세요.따라서 작동 방식은 Dog 클래스에서 사용하는 메서드와 속성을 포함하지 않는 Pluggable이라는 비열한 빈 클래스입니다.따라서 그런 일이 발생하면 마법의 방법이 우리를 사로잡을 것입니다.이것이 바로 3부와 4부에서 Dog 클래스 자체가 아닌 Pluggable 클래스에서 파생된 객체를 다루는 이유입니다.대신, Plugin 클래스가 Dog 객체를 "터치"하도록 합니다.(만약 제가 모르는 디자인 패턴이 있다면 알려주세요.)

그만큼 그리고 경청자 방법이 가장 일반적으로 사용되지만 다른 작업도 수행할 수 있습니다.앱의 크기와 코드 보기를 허용할 사람(FOSS 스크립트 또는 내부 항목)에 따라 플러그인 허용 방법에 큰 영향을 미칩니다.

kdeloach에는 좋은 예가 있지만 그의 구현과 후크 기능은 약간 안전하지 않습니다.당신이 쓴 PHP 앱의 성격과 플러그인이 어떻게 어울리는지에 대한 더 많은 정보를 제공해 주시기 바랍니다.

나에게서 kdeloach를 +1합니다.

다음은 제가 사용한 접근 방식입니다. 이는 일종의 관찰자 패턴인 Qt 신호/슬롯 메커니즘에서 복사하려는 시도입니다.객체는 신호를 방출할 수 있습니다.모든 신호에는 시스템에 ID가 있습니다. Sender의 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();

?>

가장 쉬운 방법은 Jeff의 조언을 따르고 기존 코드를 살펴보는 것입니다.Wordpress, Drupal, Joomla 및 기타 잘 알려진 PHP 기반 CMS를 살펴보고 API 후크의 모양과 느낌을 살펴보십시오.이렇게 하면 이전에 생각하지 못했던 아이디어를 얻어 상황을 좀 더 난잡하게 만들 수도 있습니다.

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

라는 깔끔한 프로젝트가 있습니다. 큰가시등 PHP에서 플러그인을 처리하기 위한 많은 작업을 처리하는 Yahoo의 Matt Zandstra가 작성했습니다.

플러그인 클래스의 인터페이스를 강화하고 명령줄 인터페이스를 지원하며 시작하고 실행하는 것이 그리 어렵지 않습니다. 특히 PHP 건축가 잡지.

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

그만큼 Zend 프레임워크 많은 후킹 방법을 사용하며 매우 훌륭하게 구성되었습니다.살펴보시면 좋은 시스템이 될 것입니다.

나는 여기에 있는 대부분의 답변이 웹 애플리케이션에 로컬인 플러그인, 즉 로컬 웹 서버에서 실행되는 플러그인에 관한 것 같다는 사실에 놀랐습니다.

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

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

이렇게 하면 원격 서버가 애플리케이션에서 전송한 정보를 기반으로 작업을 수행할 수 있는 애플리케이션에 제공된 URL(예: https를 통해)에 대해 cURL 호출을 수행하기만 하면 됩니다.

이는 두 가지 이점을 제공합니다.

  1. 로컬 서버에서 코드를 호스팅할 필요가 없습니다(보안).
  2. 코드는 PHP 이외의 다른 언어로 원격 서버(확장성)에 있을 수 있습니다(이식성).
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top