PHP Design Patterns: Das Registry Pattern

Rainer Schulz » 02. Januar 2012 um 14:53 Uhr

6 Kommentare

PHP 

Vorwort

Ich beschäftige mich schon seit einiger Zeit mit Design Patterns im Softwaredesign, vor allem mit der Umsetzung in PHP. Aus diesem Grund möchte ich euch heute das Registry Pattern beziehungsweise meine Registryklasse vorstellen.

Wozu eine Registryklasse?

Da meine Projekte, in der Regel, alle objektorientiert geschrieben sind, habe ich mir überlegt, wie ich eine Globalisierung von Variablen, Konstanten und Funktionen erreichen kann.

Die Registryklasse

In einem meiner Bücher, über Design Patterns, bin ich dann über ein Intermezzo mit genau diesem Thema gestoßen. Dieses Intermezzo hat mich dazu bewegt mir eine Registryklasse zu schreiben, welche dann nur noch in den anderen Klassen bekannt gemacht werden muss (beziehungsweise das Objekt der Klasse).

Eine kleine Info am Rande

Meine Registryklasse benutzt das Singleton Pattern welches nur eine einmalige Instanzierung der Klasse erlaubt.

Registryklasse:
/**
 * Registerklasse.
 *
 * @author Rainer Schulz <‑‑‑‑‑‑@‑‑‑‑‑‑‑>
 * @link http://raischblog.de/
 * @copyright 2011 ‑ Rainer Schulz
 * @license CC BY‑NC‑SA 3.0 <http://creativecommons.org/licenses/by‑nc‑sa/3.0/>
 * @version 1.0 29/11/2011 18:41
 */
class RsRegistry
{
    // Klasseneigenschaften ====================================================

    private static $instance = null; // Speichert die Instanz der Klasse

    // Objekteigenschaften =====================================================

    /**
     * Array fuer die Variablen.
     * 
     * @access private
     */
    private $register = array();

    /**
     * Array zum Markieren von Konstanten.
     * 
     * @access private
     */
    private $readOnly = array();

    // Konstruktor und Destruktor ==============================================

    /**
     * Der Konstruktor.
     * 
     * @access private Instanzieren nur innerhalb dieser Klasse
     * @return object gibt ein neues Objekt der Klasse zurueck
     */
    private function __construct() {}

    /**
     * Der Destruktor.
     * 
     * @access public
     * @return void gibt nichts zurueck
     */
    public function __destruct() {}

    // Klassenmethoden =========================================================

    /**
     * Die getInstance‑Methode des Singleton‑Pattern.
     * Die Methode ist statisch (ohne Objekt aufrufbar).
     * Da der Konstruktor private ist, kann nur hier ein
     * Objekt der Klasse erstellt oder, wenn schon ein Objekt existiert,
     * geladen werden.
     * 
     * @access public
     * @return object gibt das Objekt der Klasse zurueck
     */
    public static function getInstance()
    {
        // wenn '$instance' noch auf 'null' steht
        if ( self::$instance === null ) {
            // wird einmalig ein Objekt dieser Klasse erzeugt
            self::$instance = new self();
        }

        // gibt das Objekt der Klasse zurueck
        return self::$instance;
    }

    // Objektmethoden ==========================================================

    /**
     * Methode zum setzen einer Konstanten.
     * 
     * @access public
     * @param string $key Name der Konstante
     * @param mixed $value Inhalt der Konstante
     * @return void gibt nichts zurueck
     */
    public function define( $key, $value )
    {
        if ( !$this‑>exists( $key ) ) {
            $this‑>register[$key] = $value;
            $this‑>readOnly[$key] = true;
            //return $this;
        } else {
            die(
                '<h1>Error: Constant \'<em>'.$key.'</em>\' is already created '.
                'in Registry!</h1>'
            );
        }
    }

    /**
     * 'Magische' Methode zum setzen einer Variable.
     * 
     * @access public
     * @param string $key Name der Variable
     * @param mixed $value Inhalt der Variable
     * @return void gibt nichts zurueck
     */
    public function __set( $key, $value )
    {
        if ( $this‑>isConstant( $key ) ) {
            die(
                '<h1>Error: Cannot override Constant \'<em>'.$key.
                '</em>\' in Registry!</h1>'
            );
        } else {
            $this‑>register[$key] = $value;
        }
    }

    /**
     * 'Magische' Methode zum holen einer Variable.
     * 
     * @access public
     * @param string $key Name der Variable
     * @return mixed gibt die Variable zurueck
     */
    public function __get( $key )
    {
        return $this‑>exists( $key ) ?
            $this‑>register[$key] :
            die(
                '<h1>Error: Key \'<em>'.$key.
                '</em>\' not found in Registry!</h1>'
            );
    }

    /**
     * 'Magische' Methode zum aufrufen einer Funktion.
     * 
     * Lambdafunktionen bzw anonyme Funktionen stehen ab PHP 5.3 zur Verfuegung.
     * 
     * @access public
     * @param string $key Name der Funktion
     * @param mixed $args Parameter der Funktion
     * @return function gibt die Funktion zurueck
     */
    public function __call( $key, $args )
    {
        if ( $this‑>exists( $key ) && is_callable( $this‑>register[$key] ) ) {
            return call_user_func_array( $this‑>register[$key], $args );
        }

        trigger_error( $key.' is not callable.', E_USER_ERROR );
    }

    /**
     * Methode entfernt eine gesetzte Variable.
     * 
     * @access public
     * @param string $key Name der zu entfernenden Variable
     * @return bool gibt einen Wahrheitswert zurueck
     */
    public function remove( $key )
    {
        if ( $this‑>isConstant( $key ) ) {
            die(
                '<h1>Error: Cannot remove Constant \'<em>'.$key.
                '</em>\' in Registry!</h1>'
            );
        } elseif ( $this‑>exists( $key ) ) {
            unset( $this‑>register[$key] );
            return true;
        }

        return false;
    }

    /**
     * Methode prueft, ob eine Variable/Konstante gesetzt ist.
     * 
     * @access public
     * @param string $key Name der zu pruefenden Variable/Konstante
     * @return bool gibt einen Wahrheitswert zurueck
     */
    public function exists( $key )
    {
        return array_key_exists( $key, $this‑>register );
    }

    /**
     * Methode prueft, ob $key eine Konstante ist.
     * 
     * @access public
     * @param string $key Name der zu pruefenden Variable/Konstante
     * @return bool gibt einen Wahrheitswert zurueck
     */
    public function isConstant( $key )
    {
        return array_key_exists( $key, $this‑>readOnly ) &&
               $this‑>readOnly[$key] === true ? true : false;
    }

    /**
     * Ueberschiebene Methode __clone() verhindert das Klonen des Objektes.
     * 
     * @access public
     */
    public function __clone()
    {
        trigger_error( 'Cloning of this object is not allowed.', E_USER_ERROR );
    }
}

Arbeiten mit der Registryklasse

Objekt erzeugen/holen

Ein Objekt der Klasse bekommt man nur über die getInstance()-Methode des Singleton Pattern.
$reg = RsRegistry::getInstance();

Variablen

Variablen speichern

Variablen können mit der magischen Methode __set(), wie folgt, gespeichert werden.
$reg‑>varName1 = 'String';
$reg‑>varName2 = 123;
$reg‑>varName3 = 1.23;
$reg‑>varName4 = array( 'eins' => 'one', 'zwei' => 'two' );
$reg‑>varName5 = $var;

Variablen holen

Variablen können mit der magischen Methode __get(), wie folgt, geholt werden.
$int = $reg‑>varName2;
echo $reg‑>varName1;
print_r( $reg‑>varName4 );

Variablen löschen

Variablen können mit der Methode remove(), wie folgt, gelöscht werden.
$reg‑>remove( 'varName3' );
$reg‑>remove( 'varName5' );

Konstanten

Konstanten speichern

Konstanten können mit der Methode define(), wie folgt, gespeichert werden.
$reg‑>define( 'BASE', 'http://raischblog.de' );
$reg‑>define( 'V2', 'http://v2.raischblog.de' );

Konstanten holen

Konstanten können mit der magischen Methode __get(), wie folgt, geholt werden.
$root = $reg‑>BASE;
echo $reg‑>V2;

Konstanten löschen

Konstanten können nicht gelöscht werden.

Funktionen

Funktionen speichern

Funktionen können mit der magischen Methode __set(), wie folgt, gespeichert werden.
$reg‑>getFullDateString = function ( $timestamp ) {
    return date( 'F d, Y ‑ H:i', $timestamp );
}

Funktionen holen

Funktionen können mit der magischen Methode __call(), wie folgt, aufgerufen werden.
echo $reg‑>getFullDateString( time() );

Funktionen löschen

Funktionen können mit der Methode remove(), wie folgt, gelöscht werden.
$reg‑>remove( 'getFullDateString' );

Abschließendes

Bei Anregungen oder Fragen zur Klasse benutzt, wie immer, einfach die Kommentarfunktion.

 

Diese Artikel könnten dich auch interessieren

1. Alter eines Zeitstempels mit PHP ermitteln » 04. Mai 2011
2. Optimierung des Blogs mit PHP » 18. April 2011
3. Objektorientierte Programmierung » 29. Oktober 2010
4. Grundlagen der Programmierung » 26. Oktober 2010
5. Meine PHP Templateklasse. » 18. Oktober 2010

Diesen Beitrag teilen

Twitter Facebook Delicious Digg Bei allen Services (Mister Wong, Yigg, Infopirat etc.)

  1. 6 Kommentare zum Beitrag.

  2. 1. von Daniel Seelig am 21. März 2012 um 20:35:14 Uhr.

    Hallo,

    ich finde diese Registry Geschichte überzogen.

    Man sollte eher auf nicht globale Lösungen zurückgreifen und dazu Dependency Injection verwenden.
    Für Konstante braucht man weis gott keine Registry und dafür ist diese meiner Meinung nach auch nicht da und Gedacht.

  3. 2. von Rainer Schulz am 22. März 2012 um 19:27:16 Uhr.

    Hallo Daniel,

    DI's haben gegenüber einer Regisrty-Klasse natürlich einige Vorteile, aber eben auch Nachteile. Abhängigkeiten zu minimieren ist hier sicher ein sehr schöner Vorteil von DI's.

    Konstanten kann man sicherlich auch über eine Configklasse mit Klassenkonstanten pflegen. Nachteil dabei ist leider, dass man keine Rückgabewerte von Funktionen/Methoden oder Verkettungen nutzen kann.

    Gruß

  4. 3. von Dani Schenker am 23. März 2012 um 21:14:58 Uhr.

    Hallo Rainer,
    Sorry, das wird ein Offtopic Kommentar. Ich wollte dich aber unbedingt kurz auf meinen letzten Post im Blogszene Forum hinweisen. Vielleicht wäre das was für dich ;-)

    forum.blogszene.com/289-plugin-idee-wer-kann-helfen-bei-der-programmierung.html

    Schöne Grüsse
    Dani

  5. 4. von Rainer Schulz am 24. März 2012 um 09:29:43 Uhr.

    Huhu Dani,

    schau ich mir nachher mal an. :)

    Gruß

  6. 5. von tr0y am 25. März 2012 um 19:40:01 Uhr.

    Die Konzept der Klasse wirkt auf mich etwas inkonsequent.

    Du erduldest in der aktuellen Version Inheritance ( Vererbung ), getInstance() proklamiert aber explizit eine Instanz des selben Scopes.

    static public function getInstance() {
      self::$_instance = is_null( self::$_instance )
         ? new static
         : self::$_instance;

      return self::$_instance;
    }


    Regelt das ganze in die richtigen Bahnen, außerdem sollte der Constructor der Klasse definitiv PROTECTED sein, andernfalls kann auch nur aus dem aktuellen Scope der Klasse instanziiert werden, oder der Constructor müsste in der überliegenden Version der Klasse neu definiert werden ( kein Mehrwert, mehr Arbeit ). Willst du Inheritance bei der Registry-Klasse nicht, definier sie als "final class" statt "class".

    Über den Sinn oder Unsinn würde ich auch nicht streiten wollen, es ist gleichgültig ob du write-protected ( konstante ) Values in der Registry erlaubst oder nicht. Daten sind Daten. Allerdings würde ich Closures ( Lambda-Functions ) eher _nicht_ nutzen bzw. als Registry-Werte zulassen. Du simulierst damit Methoden-Verhalten eines Properties.

    Als alternative zu der "registred Lambda"-Sache würde ich entsprechende Adapter anbieten die entsprechende Felder in der Registry warten und Invoker konsequent zum Aufruf benutzen. Der Helper Adapater des Zend Frameworks funktioniert mit unter so.

    tr0y

  7. 6. von Rainer Schulz am 26. März 2012 um 19:53:01 Uhr.

    Hallo tr0y,

    Vererbung will ich in dieser Klasse nicht zulassen, in der aktuellen Version der Klasse ist diese deswegen auch auf 'final' gesetzt. Aus diesem Grund wird auch der Konstruktor definitiv 'private' bleiben.

    Short Conditional If's (oder Inline IF's) wie in deiner 'getInstance()'-Methode versuche ich zu vermeiden, da sie, meiner Meinung nach, die Lesbarkeit des Codes reduzieren. Auch 'late static binding' mit 'new static()' ist, in meinem Fall, ohne Vererbung dann nutzlos, kann ich also auch auf 'new self()' lassen. Wenn man Vererbung zulässt, dann ist 'new static()' sicherlich sinnvoller.

    Aktuell sieht meine 'getInstance()'-Methode so aus:
    /**
    * The factory method of the Singleton design pattern.
    *
    * Usage:
    * $objRegistry = Registry::getInstance();
    *
    * @singleton
    * @static
    * @access public
    * @return object Registry
    *                returns the only object of this class
    */
    public static function getInstance()
    {
       // if $objInstance is still 'null'-reference
       if ( null === self::$objInstance )
       {
           // create an object of this class once
           self::$objInstance = new self();
       }

       // return the object
       return self::$objInstance;
    }


    Die Möglichkeit für Closures, der Klasse, nutze ich persönlich auch nicht, wollte diese Möglichkeit aber bereitstellen. Statt der Closures nutze ich zugeschnittene Libraries, als 'abstracte' Klassen, in welchen ich thematisch zusammengehörende Methoden kapsele.

    Den Helper Adapter des ZF schaue ich mir bei Gelegenheit mal an. In der aktuellen Version habe ich noch eine Callback-Möglichkeit eingebaut, eventuell ist das ja das was Du meinst.

    So schaut die Callback aus:
    /**
    * Method to call a function on a member.
    *
    * Usage:
    * $obj->callback(
    *     string Membername,
    *     function Callback
    *     [, mixed Argument [, mixed Argument ... ]]
    * );
    *
    * @access public
    * @return boolean
    */
    public function callback()
    {
       if ( 2 > func_num_args() )
       {
           trigger_error(
               'Missing argument for callback(), '
               . 'minimum of 2 arguments needed.',
               E_USER_WARNING
           );

           return false;
       }

       $arrArguments  = func_get_args();
       $strMembername = array_shift( $arrArguments );
       $funcCallback  = array_shift( $arrArguments );

       if ( false === $this->exists( $strMembername ) )
       {
           trigger_error(
               'Cant execute callback function, member '
               . $strMembername . ' dont exists in registry.',
               E_USER_WARNING
           );

           return false;
       }

       if ( false === is_string(   $this->arrMembers[$strMembername] ) &&
            true  === is_callable( $this->arrMembers[$strMembername] ) )
       {
           trigger_error(
               'Cant execute callback function on closure '
               . $strMembername . ' in registry.',
               E_USER_WARNING
           );

           return false;
       }

       if ( false === is_callable( $funcCallback ) )
       {
           trigger_error(
               'Given callback function is not callable.',
               E_USER_ERROR
           );

           return false;
       }

       array_unshift(
           $arrArguments,
           $this->arrMembers[$strMembername]
       );

       $this->arrMembers[$strMembername] = call_user_func_array(
           $funcCallback,
           $arrArguments
       );

       return true;
    }


    Gruß

  8. Schreib einen Kommentar.
  9. Kommentare sind deaktiviert.