Event Handling mit PHP

Heute Vormittag beklagte sich Sascha kurz darüber, dass es in PHP kein vernünftiges EventHandling gibt und man dieses erst aufwendig selbst umsetzen müsse. Ich fand das Thema ziemlich interessant und habe mich hier mal schlau gemacht, welche Möglichkeiten PHP bietet, um ein vernünftiges Event Handling zu realisieren. Beim googlen nach Informationen bin ich auf ein schönes Beispiel auf php.de gestoßen. Allerdings habe ich mich gefragt, ob PHP selbst nicht schon Möglichkeiten bietet, die man in diesem Zusammenhang nutzen könnte. Aber zunächst mal die Basics.

Was sind Events?

In Programmiersprachen wir Javascript z.B. gibt es so genannte Events, welche eigentlich nichts anderes als Ereignisse sind, die während der Laufzeit eines Programms auftreten können. So ein Ereignis könnte zum Beispiel das Auftreten eines Fehles oder der Erfolgsfall nach dem Ausführen einer Funktion sein. Gerade bei größeren Software Projekten macht es Sinn seine eigenen Events an bestimmten Punkten im Programm festzuhalten. Diese so genannten Hooks führen zu einer hohen Erweiterbarkeit, ohne direkt in der vorhandenen Software arbeiten zu müssen. Schließlich zerlege ich ja auch nicht gleich den kompletten Javascript Kern, wenn ich auf einen Mausklick reagieren möchte. Halten wir also fest, dass Events einfach nur Ereignisse sind, die auftreten können, wenn das Programm ausgeführt wird.

Wie geht das mit PHP?

Ab PHP5.1 bietet die SPL das SplObserver und das SplSubject Interface an. Wie bereits im Observer Pattern beschrieben, benötigt man zum vernünftigen EventHandling lediglich ein Subjekt, welches eine unbestimmte Anzahl von Observern erhält. Das Subjekt informiert die Observer dann automatisch, sobald eine bestimmte Aktion ausgeführt wurde. Somit hätten wir also alles, was wir für ein vernünftiges Event Handling benötigen.

Das einfache Beispiel

In dem eingangs beschriebenen Beispiel auf php.de geht es einfach gesagt um Personen, die ein Gespräch führen. Sofern eine Person etwas fragt, sollen alle anderen beteiligten Personen etwas sagen. Diese Situation ist wie geschaffen für das Event Handling. Unser Event, also unser Ereignis, auf das reagiert werden soll, ist die Frage einer einzelnen Person dieser Gruppe. Definieren wir doch einfach mal das Objekt für die Gruppe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Group implements SplSubject {
    protected $persons = null;
    protected $observers = null;
    protected $speaker = null;	
 
    public function __construct() {
        $this->persons = new SplObjectStorage();
        $this->observers = new SplObjectStorage();
 
        $onSpeakObserver = new OnSpeakObserver($who, $what);
        $this->attach($onSpeakObserver);
    }
 
    public function add(Person $person) {
        $this->persons->attach($person);
    }
 
    public function speak(Person $who, $what) {
        echo $who . " sagt: " . $what . "<br />";
 
        $this->speaker = $who;	
        $this->notify();
    }
 
    public function getSpeaker() {
	return $this->speaker;
    }
 
    public function getGroup() {
	return $this->persons;
    }
 
    public function attach(SplObserver $observer) {
	$this->observers->attach($observer);
    }
 
    public function detach(SplObserver $observer) {
	$this->observers->attach($observer);
    }
 
    public function notify() {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }
}

Die Gruppe funktioniert hier als unser Subjekt und implementiert das SplSubject Interface. Mit dem Interface sind die Methoden attach, detach und notify zwingend zu integrieren. Mit der Methode attach() bin ich in der Lage einen Observer an einem beliebigen Punkt festzulegen. Wir legen unseren Observer direkt im Konstruktor des Objektes fest, da dieser ja für alle beteiligten Personen gelten soll. Jedes Mal, wenn jemand etwas sagt, wird die Methode speak() ausgeführt. Hier wird dann auch unser Observer mit der Methode notify() ausgeführt. Das ist so, als würde ich einem schlafenden Freund auf die Schulter klopfen und dieser erschreckt sich dann und macht irgendwas. In diesem Fall soll der Freund dann einfach nur „ich!“ sagen. Ach ja … die Freunde. Die Freunde sind Personen, die wie folgt aussehen:

1
2
3
4
5
6
7
8
9
10
11
class Person {
    protected $name = '';
 
    public function __construct($name) {
	$this->name = $name;
    }
 
    public function __toString() {
        return $this->name;
    }
}

Das Person Objekt nimmt lediglich den Namen des Freundes auf, so dass wir auch wissen, wer sich da in unserem illustren Freundeskreis befindet. Jetzt fehlt eigentlich nur noch der Observer.

1
2
3
4
5
6
7
8
9
class OnSpeakObserver implements SplObserver {
    public function update(SplSubject $subject) {
        foreach ($subject->getGroup() as $person) {
            if ($person !== $subject->getSpeaker()) {
                echo $person . " sagt: ich!<br />";
            }
        }
    }
}

Der Observer implementiert das SplObserver Interface, welches einfach nur die update() Methode mit sich bringt. Mit dieser Methode führen wir aus, was auch immer wir ausführen möchten. In diesem Fall sollen alle mit „ich!“ antworten, außer der Fragende selbst. Die update() Methode greift auf das Subjekt zurück, welches den Observer ausgelöst, oder um es fachlich auszudrücken, abgefeuert hat. Somit sind alle Methoden des Gruppen Objekts verfügbar und wir können auswerten, wer etwas gesagt hat. Der einfache Aufruf kann dann so aussehen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Gruppe eröffnen
$friends = new Group();
 
$marcel = new Person('Marcel');
$friends->add($marcel);
 
$biby = new Person('Biby');
$friends->add($biby);
 
$nicole = new Person('Nicole');
$friends->add($nicole);
 
$sarah = new Person('Sarah');
$friends->add($sarah);
 
$sushi = new Person('Sushi');
$friends->add($sushi);
 
// Gespräch auslösen
$friends->speak($marcel, 'Wer hat mich lieb?');
 
// Ausgabe:
// Marcel sagt: Wer hat mich lieb?
// Biby sagt: ich!
// Nicole sagt: ich!
// Sarah sagt: ich!
// Sushi sagt: ich!

Denkbare Beispiele in der Praxis

Natürlich handtiere ich im Alltag nicht mit Objekten umher, die meine Freunde darstellen. Hier geht es um sehr viel komplexere Dinge. Spontan fallen mir hier Klassen ein, die ein Formular darstellen und von einem Formular Interface und einer Formular Abstraktion abgeleitet sind. So könnte man im abstrakten Formular die Events abfeuern, die man in den abgeleiteten Formular Klassen definiert hat. Bei der Validierung eines Formulars könnte man so einen Fehler Observer etablieren. Beim Absenden des Formulares einen Submit Observer, beim Speichern der Formulardaten einen Success Observer, u.s.w. Somit könnten sich alle abgeleiteten Formularklassen einhaken und weitere Observer benennen, die zu den festgelegten Zeitpunkten ausgeführt werden. Somit wäre man in puncto Erweiterbarkeit des Scripts extrem flexibel.

About Author: Marcel
Ich bin Senior PHP Developer bei MM Newmedia. Seit 2005 bin ich begeisterter Webentwickler und arbeite als Freelancer für namenhafte Firmen und entwickle jede Menge abgefahrenes Zeug und berichte darüber in meinem Blog.

Kommentar verfassen