11
Feb
2011
admin

Das Registry Pattern oder warum man globale Variablen vermeiden sollte

Ich habe in der Vergangenheit viele Projekte gesehen, bei denen man das Gefühl hatte, sie bestehen nur aus globalen Variablen. In jeder Funktion stand mindestens ein mal etwas wie:

global $globalVars;

Da wurden Konfigurationen, Pfade, Benutzerinformationen, Datumswerte in allen erdenklichen Varianten oder Farbschemas gespeichert, so dass diese globalen Variablen sich am Ende so aufgebläht haben, dass wohl keiner mehr wusste, was überhaupt in diesen Variablen an Daten gehalten wird. Ein großes Problem, das sich daraus ergibt, ist z.B. die fehlende Kontrolle beim speichern von globalen Variablen. Hierzu ein Beispiel:

class foo {
  
  public function bar() {
    // ...
  }
}

$globalVars['foo'] = new foo();
doSomethingWithMyFile('/my/file.txt');
doSomethingWithFoo();

function doSomethingWithMyFile($file) {
  global $globalVars;

  $globalVars['foo'] = $file;
}

function doSomethingWithFoo() {
  global $globalVars;

  $fooBar = $globalVars['foo'];
  $fooBar->bar();
}

Dieser Code ist auf den ersten Blick in Ordnung. Auf den zweiten Blick fällt jedoch auf, dass die Funktion doSomethingWithMyFile() die globale Variable $globalVars['foo'] verändert. In der Funktion doSomethingWithFoo(), die gleich danach aufgerufen wird, erwartet man allerdings, dass in eben dieser globalen Variablen eine Instanz von foo ist. Das Resultat ist ein fataler Error, da wir versuchen, eine Methode auf einem nicht existierenden Objekt aufzurufen. Das ist in diesem Fall noch recht harmlos, da wir eine relativ aussagekräftige Fehlermeldung bekommen und den Fehler schnell finden und beheben können. Es gibt aber auch Fälle, in denen das nicht so schnell auffällt. Ein weiteres Beispiel:

// Programmierer 1
$globalVars['file_dir'] = '/ausserhalb/des/documentRoot/';
copy('dateiVonP1.txt', $globalVars['file_dir']);

// Programmierer 2
$globalVars['file_dir'] = '/innerhalb/des/documentRoot/';
copy('dateiVonP2.txt', $globalVars['file_dir']);

// Programmierer 1
copy('databaseConfig.ini', $globalVars['file_dir']);

/** Houston, wir haben ein Problem **/

Dummerweise hat Programmierer 2 nicht gewusst, dass Programmierer 1 die globale Variable $globalVars['file_dir'] bereits für seine Zwecke gefüllt hat und setzt dort einen anderen Datei Pfad. Programmierer 1, dessen Code nach dem von Programmierer 2 läuft, kopiert nun die Datenbank Konfigurationsdatei (inkl. Benutzername und Passwort) versehentlich in ein öffentliches Verzeichnis. Ärgerliche Sache.

Wie man sieht, verliert man bei globalen Variablen irgendwann den Überblick und reißt so, unter Umständen, große Sicherheitslücken ins System. Außerdem macht das versehentliche Überschreiben von Werten das Debuggen erheblich schwerer. Um trotzdem in allen Schichten Zugriff auf bestimmte Variablen zu erhalten, ohne sich jedoch mit den beschriebenen Nachteilen von globalen Variablen herumschlagen zu müssen, sollten wir einen genaueren Blick auf das Registry Pattern werfen.

Das Registry Pattern ist im Grunde nichts weiter, als ein Array, welches unsere Variablen, bzw. Objekte oder Informationen, im gesamten Projekt verfügbar macht. Dazu werden wir, unter Zuhilfenahme eines Singleton Patterns, als erstes einen globalen Zugriffspunkt für unsere Registry erstellen.

class Registry {

  protected static $instance = null;

  public static function getInstance() {
    if (self::$instance === null) {
      self::$instance = new Registry();
    }
    return self::$instance;
  }

  protected function  __construct() { }

  private function  __clone() { }
}

Mit dem Singleton Pattern ist es nun möglich eine Instanz von Registry zu holen, indem wir die statische Methode Registry::getInstance() aufrufen. Sollte noch keine Instanz erzeugt worden sein, wird sie mit diesem Aufruf erzeugt, ansonsten wird die bereits bestehende Instanz zurückgeliefert. Dadurch, dass der Konstruktor protected und das Klonen private ist, stellen wir sicher, dass immer die selbe Instanz benutzt wird, da sie nur mit Registry::getInstance() geholt werden kann. Unser globaler Zugriffspunkt wäre damit erstellt.

Nun brauchen wir natürlich noch zwei Methoden um Daten in der Registry zu speichern, bzw. aus der Registry abzurufen. Um es möglichst einfach zu halten, werden wir diese Methoden ebenfalls statisch machen. Außerdem brauchen wir ein Array, in dem wir die Werte oder Objekte Speichern können.

class Registry {

  protected static $instance = null;
  protected $values = array();

  public static function getInstance() {
    if (self::$instance === null) {
      self::$instance = new Registry();
    }
    return self::$instance;
  }

  protected function  __construct() { }

  private function  __clone() { }

  public static function set($index, $value) {
    $instance = self::getInstance();

    if (isset($instance->values[$index])) {
      throw new Exception("There is allready an entry for key '$index'");
    }

    $instance->values[$index] = $value;
  }

  public static function get($index) {
    $instance = self::getInstance();

    if (!isset($instance->values[$index])) {
      throw new Exception("There is no entry for key '$index'");
    }

    return $instance->values[$index];
  }
}

Damit haben wir eine recht einfache aber funktionierende Registry. Probieren wir es, anhand des Beispiels unserer beiden Programmierer, ein mal aus.

// Programmierer 1
Registry::set('file_dir', '/ausserhalb/des/documentRoot/');
copy('dateiVonP1.txt', Registry::get('file_dir'));

// Programmierer 2
Registry::set('file_dir', '/innerhalb/des/documentRoot/');
copy('dateiVonP2.txt', Registry::get('file_dir'));

// Programmierer 1
copy('databaseConfig.ini', Registry::get('file_dir'));

Dieses Mal würde die Zeile 6 eine Exception werfen, da bereits ein Element mit dem Index file_dir existiert. Programmierer 2 kann nun einen anderen Index definieren, um seinen Pfad zu speichern und die Datenbank Konfiguration von Programmierer 1 landet genau dort, wo er es erwartet.

Wir sehen also, dass es durchaus Sinn macht, eine Registry zu nutzen und auf globale Variablen zu verzichten. Die Frage ist nun, wie man verhindert, dass Unmengen von Daten in der Registry liegen, die kaum oder nie wieder gebraucht werden, wie es bei globalen Variablen oft der Fall ist.

Die Antwort ist einfach: Wir können es nicht verhindern :)
Hier ist nicht unser Pattern gefragt, sondern der Programmierer. Man sollte sich genau überlegen, welche Daten man in der Registry oder in globalen Variablen ablegt. Es macht keinen Sinn dort Datumswerte in allen erdenklichen Formaten zu speichern, nur falls man sie irgendwo, irgendwann vielleicht mal benötigt. Wenn man oft mit Datumswerten arbeitet sollte man lieber ein Objekt in der Registry ablegen, dem man ein Datumsformat übergibt, und welches dann das richtige Datum zurückgibt, wie z.B. Registry::get('date')->getDate('yyyy-mm-dd').

Egal ob man nun globale Variablen oder eine Registry nutzt. Datenmüll ist immer der falsche Weg.

Trackback-URL für diesen Beitrag

http://www.abouttheweb.de/trackback/635

Ähnliche Artikel

X
Laden