Artikelformat

Marker Interfaces – ein Beispiel

In der wirklich kurzen Einführung zum Thema Interfaces habe ich die Marker-Interfaces erwähnt. Diese haben dann gleich entsprechende Nachfragen ergeben. Um den Wissensdurst zu stillen gibt es hier nun ein Beispiel, wie man ein solches Interface einsetzen kann.

Als Basis überlegen wir uns erst, was sich als Beispiel eignet. Ich schlage einen Object-Cache vor, in dem man seine Daten ablegen kann. Ich stelle eine wirklich simple Implementierung vor. Man könnte den Cache sicher auch mit einem memcached verbinden oder ähnliches. Aber das Caching von Objekten ist nur ein Sekundärthema, also muss die Idee nicht vollkommen ausgearbeitet sein – wie ich meine.

Um das Interface-Spiel auf die Spitze zu treiben wird zuerst ein Interface definiert, dass CacheManager heißt und addObject, getObject und deleteObject erfordert. Damit kann man ein Objekt hinzufügen, aus dem Cache lesen und löschen.

1
2
3
4
5
6
interface CacheManager {
 
    public function addObject($key, IsCachable $object, $overwrite = false);
    public function getObject($key);
    public function deleteObject($key);
}

Wie man hier bereits schon sehen kann ist das Objekt, das man in den Cache stecken kann vom Typ IsCachable bzw. implementiert das Objekt dieses Interface. Hierbei handelt es sich um den Kern dieses Beitrags. Das ist nämlich das Marker-Interface. Man kann nun erkennen, dass der CacheManager nur Objekte dieses Typs zulässt. Später werden wir auch den Fehlerfall betrachten. Bis dahin definieren wir aber zuerst das Marker-Interface IsCachable:

1
2
3
interface IsCachable {
    // Marker-Interface
}

Jetzt definieren wir den eigentlichen Cache. Es handelt sich um die Klasse NonPersistentCache. Dieser Cache hält die Daten nur während der Laufzeit des Skripts vor – daher auch nicht-persistent. Es wurde nur wert auf die Funktion gelegt, Sicherheitsaspekte fehlen vollständig, aber das ist auch nicht das Thema:

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
class NonPersistentCache implements CacheManager {
 
    private $internalCache = array();
 
    public function __construct() {
 
    }
 
    public function addObject($key, IsCachable $object, $overwrite = false) {
        if ($overwrite || !isset($this->internalCache[$key])) {
            $this->internalCache[$key] = $object;
        }
    }
 
    public function getObject($key) {
        if (isset($this->internalCache[$key])) {
            return $this->internalCache[$key];
        }
        else {
            return null;
        }
    }
 
    public function deleteObject($key) {
        unset($this->internalCache[$key]);
    }
}

Die Objekte werden in einem assoziativen Array abgelegt und über einen Key identifiziert. Man kann ein bereits vorhandenen Key mit einem neuen Objekt belegen. Wie bereits im Interface definiert geht auch das Auslesen und Löschen. Alles keine Hexerei, aber wir benötigen nun auch noch ein Objekt, dass man im Cache ablegen kann. Hierfür gibt es die Klasse CachableThing. Dabei handelt es sich um eine Klasse mit einem Konstruktor der 2 Werte erwartet und die eine getInfo Methode bereitstellt. Die konkateniert die angegebenen Infos und gibt den neuen String zurück. Es gibt also keine große Funktion, aber diese Klasse implementiert das IsCachable-Interface, was eben der spannende Punkt ist:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CachableThing implements IsCachable {
 
    private $info1;
    private $info2;
 
    public function __construct($info1, $info2) {
      $this->info1 = $info1;
      $this->info2 = $info2;
    }
 
    public function getInfo() {
        return ($this->info1 . $this->info2);
    }
}

Jetzt haben wir alle Komponenten zusammen und können den Cache nutzen und darin Objekte ablegen. Wir testen das Vorgehen einmal mit einem korrekten Objekt und einmal mit einem String, der das IsCachable Interface natürlich nicht implementiert und schauen einfach, was passiert:

1
2
3
4
5
6
7
8
$cache = new NonPersistentCache();
$object1 = new CachableThing("foo","bar");
 
$cache->addObject("object1",$object1);
$found = $cache->getObject("object1");
echo $found->getInfo();
 
$cache->addObject("object2","only_text");

Object1 implementiert unseren Cachable-Marker, also ist zu erwarten, dass dieses Objekt in den Cache eingefügt und anschließend wieder ausgelesen werden kann. Somit sollte das echo die Information ausgeben, die man erwartet. In diesem Fall foobar. Was passiert nun, wenn man einen String in den Cache stecken möchte? Die Ausgabe sieht dann insgesamt so aus:

1
2
3
4
$ php index.php
foobar
 
Catchable fatal error: Argument 2 passed to NonPersistentCache::addObject() must implement interface IsCachable, string given, called in /PHP/index.php on line 28 and defined in /PHP/NonPersistentCache.php on line 15

Also wird richtig erkannt, dass ein String nicht cachebar ist im Sinne unseres Caches. PHP erkennt, dass ein Fehler aufgetreten ist, aber die Zend Engine ist nicht in einem instabilen Zustand, was bedeutet, dass man diesen Fehler zum Benutzer weitergeben kann und dieser darauf reagiert – mit einem „Error Handler“ bspw.

Nun definieren wir noch kurz einen Error-Handler, der den „Catchable Fatal Error“ in eine Exception verwandelt, auf die man im Quelltext mit einem try-catch reagieren kann. Also sieht der Aufruf insgesamt so aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function myErrorHandler($errno, $errstr, $errfile, $errline) {
  if ( E_RECOVERABLE_ERROR===$errno ) {
    throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
  }
  return false;
}
set_error_handler('myErrorHandler');
 
$cache = new NonPersistentCache();
$object1 = new CachableThing("foo","bar");
 
$cache->addObject("object1",$object1);
 
$found = $cache->getObject("object1");
echo $found->getInfo();
 
echo "\n";
 
try {
	$cache->addObject("object2","only_text");
}
catch (ErrorException $e) {
	echo "tried to add non cacheable object\n";
}

Nun wird also der Catchable Fatal Error in eine Exception umgewandelt und diese kann im Quelltext verarbeitet werden. Das Hinzufügen des Strings sichern wir per try-catch ab und erwarten bei einer Ausführung, dass der Programmfluss nicht mehr unterbrochen wird.

1
2
3
$ php index.php 
foobar
tried to add non cacheable object

Somit kann man nicht nur den herkömmlichen Programmfluß sicherstellen, sondern auch trickreichere Konstruktionen (bspw Reflection) sichern.

Schreibe einen Kommentar

Pflichtfelder sind mit * markiert.