Einfaches Hook-System mit PHP realisieren

Zur Situation

Mal angenommen wir wollen ein Programm/Framework schreiben, das einem externen Entwickler die Möglichkeit bieten soll seinen eigenen Code zu implementieren ohne dass er den Core Code ändert. Mit anderen Worten wir erstellen eine API für externe Entwickler, die unser Programm/Framework erweitern wollen.

Was wir brauchen

In diesem Beispiel erstellen wir eine User-Klasse mit verschiedenen Methoden. Die User-Klasse benutzt das Hook-System um bei bedarf die User-Klasse von außen zu erweitern.

Ich habe fünf Dateien erstellt, die mir Helfen das ganze zu veranschaulichen.

  • Observer.php
  • User.php
  • config.php
  • customCode.php
  • index.php

Config.php

Der Name "Config" ist frei von mir gewählt worden. Dort liegt nur unsere Globale Hook variable. Diese soll ein Array sein. Später werden dort unsere Funktionen gespeichert.

<?php
//leeres array
$GLOBALS['hook'] = array();

Observer.php

Wir erstellen einen Trait mit einem Konstruktor und zwei weiteren Methoden. Der Konstruktor überprüft, ob die Globale Hook variable gesetzt und ob es ein Array ist.

Die watch Methode erwartet zwei Parameter ein String "channel" und einer Hook Funktion. Diese Methode speichert die übergebene Hook Funktion in die Globale-Hook variable, die wir zuvor in der config.php erstellt habe. Der channel sorgt dafür das die Hook Funktion an der richtigen Stelle aufgerufen wird.

Die subscribe Methode erwartet einen String als Parameter. Diese Methode sucht in der Globalen Hook variable nach einem bestimmten channel und ruft diesen auf.

<?php

trait Observer{

    /**
     * check if $GLOBALS['hook'] isset and is array
     */
    public function __construct()
    {
        if( !isset( $GLOBALS['hook'] ) && !is_array( $GLOBALS['hook'] ) )
        {
            return;
        }
    }

    /**
     * save hook function in $GLOBALS['hook']
     * @param String, function
     */
    public function watch($channel, $func){

        if( !isset( $GLOBALS['hook'][$channel] ) ){

            $GLOBALS['hook'][$channel] = array();   

        }

        array_push($GLOBALS['hook'][$channel], $func);

    }

    /**
     * loop through $GLOBALS['hook'] and call hook functions
     * @param String
     */
    private function subscribe($channel){

        if( isset( $GLOBALS['hook'][$channel] ) ){

            foreach( $GLOBALS['hook'][$channel] as $func ){

                $func();

            }

        }

    }

}

User.php

Die User.php ist eine Beispiel Klasse, anhand dieser Klasse, werde ich zeigen wie man den "Observer Trait" verwenden kann.

Wir erstellen eine Klasse mit einigen Methoden. Wir binden unsere Observer Datei mit include "Observer.php" ein. Am anfang unserer Klasse fügen wir ein "use Observer" hinzu und sagen der User Klasse diese soll die Methoden und Eigenschaften der Observer-Klasse erben.

Danach können wir unseren Methoden, an ganz bestimmten Stellen erlauben einen fremden Code auszuführen. Indem wir folgende Methode aufrufen $this->subscribe("channelName")

<?php

/**
 * Eine Beispiel Klasse
 */

include 'Observer.php';

class User{

    use Observer;

    public function update(){

        echo 'User wurde editiert! </br>';
        $this->subscribe('onUserUpdate');

    }

    public function destroy(){

        //bevor Methode aufgerufen wird
        $this->subscribe('beforeUserDestroy');

        echo 'Lösche den User! </br>';

        //nachdem Methode aufgerufen worden ist
        $this->subscribe('afterUserDestroy');

    }

    public function add(){

        echo 'Füge einen User hinzu! </br>';
        $this->subscribe('onUserInsert');

    }

}

customCode.php

Diese Datei erweitert nun unsere User-Klasse, ohne diese zu überschreiben und zwar nur an den Stellen an denen wir es auch erlauben. Alles was wir machen müssen ist es die User-Klasse zu initialisieren und die watch Methode aufzurufen. Dieser watch Methode übergeben wir zwei Parameter einen String und eine Funktion. Der String ist nichts anderes als unser channel. Die übergebene Funktion wird bei der entsprechende Methode aufgerufen.

<?php

/**
 * so könnte der code aussehen von einem entwickler, 
 * der versucht unsere User Klasse zu erweitern
 */

$costumUser = new User();


$costumUser->watch('beforeUserDestroy', function(){

    //code
    echo 'Mache was bevor du den User löscht! </br>';

});

$costumUser->watch('afterUserDestroy', function(){

    //code
    echo 'Mache was nachdem du den User gelöscht hast! </br>';

});

$costumUser->watch('onUserInsert', function(){ 

    //code
    echo 'Mache was wenn ein User Hinzugefügt wird! </br>';

});


$costumUser->watch('onUserUpdate', function(){ 

    //code
    echo 'Mache was wenn ein User editiert wird! </br>';

 });

index.php

Die index.php dient als Startpunkt des Programms.

<!DOCTYPE html>
<html>

    <head>
      <title>Einfaches Hook-System mit PHP realisieren</title>
      <meta charset="UTF-8">
    </head>

    <body>

      <h1>Beispiel</h1>  

      <?php

            include 'config.php';
            include 'user.php';
            include 'customCode.php';

            $user = new User();

            $user->update();
            $user->destroy();
            $user->add();

        ?>

    </body>

</html>

Schluss

Wir haben unsere Klasse-User erstellt und diese mit unserem Observer erweitert. Jetzt brauchen wir nur dem Entwickler die hooks( z.B beforeUserDestroy, onUserInsert, onUserUpdate .. ) zu übergeben und zu dokumentieren. Der Entwickler kann die Klasse ohne viel Aufwand erweitern.

Alexander Naumov Contao Freelancer

Alexander Naumov

Contao Freelancer, PHP Entwickler und Web Allrounder

Kontakt aufnehmen
Zurück