PHP 애플리케이션 용 플러그인을 허용하는 가장 좋은 방법

php plugins architecture hook


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

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




Answer 1 Kevin


Observer 패턴을 사용할 수 있습니다. 이를 수행하는 간단한 기능적 방법 :

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

Output:

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

Notes:

이 예제 소스 코드의 경우 확장 가능하도록하려는 실제 소스 코드 앞에 모든 플러그인을 선언해야합니다. 플러그인에 전달되는 단일 또는 다중 값을 처리하는 방법에 대한 예를 포함했습니다. 가장 어려운 부분은 각 후크에 전달되는 인수를 나열하는 실제 문서를 작성하는 것입니다.

이것은 PHP에서 플러그인 시스템을 수행하는 한 가지 방법 일뿐입니다. 더 나은 대안이 있습니다. 자세한 내용은 WordPress 문서를 확인하는 것이 좋습니다.




Answer 2 Volomike


따라서 청취 작업을 처리하기 위해 클래스 메서드를 변경해야하고 일반적인 것을 원하기 때문에 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;

Part 1에서는 PHP 스크립트 상단에 require_once() 호출을 포함 할 수 있습니다 . 플러그 가능한 것을 만들기 위해 클래스를로드합니다.

2 부에서는 클래스를로드합니다. 나는 관찰자 패턴과는 상당히 다른 클래스에 대해 특별한 작업을 할 필요가 없었습니다.

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

4 부에서는 이것이 정상적인 운영 코드이지만 실행될 것이라고 생각하는 것이 전혀 그렇게 실행되지 않는다는 점에 유의하십시오. 예를 들어 개는 이름을 'Fido'가 아니라 'Coco'라고 발표합니다. 개는 '야옹'이 아니라 '와우'라고 말합니다. 그리고 나중에 강아지 이름을보고 싶을 때 '코코'가 아닌 '다름'이라는 것을 알 수 있습니다. 이러한 모든 재정의는 3 부에서 제공되었습니다.

그럼 어떻게 작동합니까? 음, eval() (모든 사람이 "악"이라고 말함)을 배제하고 이것이 Observer 패턴이 아니라는 것을 배제합시다 . 따라서 작동 방식은 Dog 클래스에서 사용하는 메서드와 속성을 포함하지 않는 Pluggable이라는 교활한 빈 클래스입니다. 따라서 그것이 발생하기 때문에 마법의 방법이 우리에게 관여 할 것입니다. 그렇기 때문에 파트 3과 4에서 Dog 클래스 자체가 아닌 Pluggable 클래스에서 파생 된 객체를 엉망으로 만듭니다. 대신, 우리는 Plugin 클래스가 Dog 객체를 "터치"하도록합니다. (그게 제가 모르는 디자인 패턴이라면 알려주세요.)




Answer 3 w-ll


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

kdeloach에는 좋은 예가 있지만 그의 구현 및 후크 기능은 약간 안전하지 않습니다. 나는 당신이 작성한 PHP 앱의 특성과 플러그인이 어떻게 맞는지에 대한 더 많은 정보를 제공하기를 요청합니다.

나에게서 kdeloach에 +1.




Answer 4 andy.gurin


여기 제가 사용한 접근 방식이 있습니다. 이것은 일종의 Observer 패턴 인 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();

?>



Answer 5 helloandre


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

보다 직접적인 대답은 필요한 유용성을 제공하는 "include_once"파일에 일반 파일을 작성하는 것입니다. 이것은 범주로 나뉘며 하나의 거대한 "hooks.php"파일에 제공되지 않습니다. 그러나 결국에는 파일에 포함 된 파일에 점점 더 많은 종속성과 기능이 향상되므로주의해야합니다. API 종속성을 낮게 유지하십시오. IE는 포함 할 파일 수가 적습니다.