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, PHP Entwickler und Web Allrounder