Archiv

Archiv für die Kategorie ‘PHP’

Regular Expressions mit Unicode Support

13. Dezember 2011 Sven Keine Kommentare

Für alle, die es noch nicht wussten: Auch RegEx haben in allen gängigen Programmiersprachen Unicode Support.
Es gibt für alle möglichen Zeichen auch praktische Charakterklassen, wie zum Beispiel mathematische Symbole oder Währungssymbole.

Eine ausführliche Dokumentation mit vielen Beispielen findet ihr unter:
http://www.regular-expressions.info/unicode.html

KategorienPHP Tags: ,

Eine Datei kopieren und kaputte Sektoren überspringen mit PHP

27. Februar 2011 Sven Keine Kommentare

Ich stand heute mal wieder vor dem Problem kaputte Dateien von einer DVD zu retten. Für so etwas gibts es natürlich tolle Tools wie Isobuster etc. Aber diese haben ein kleines Problem: sie versuchen die Dateien in sehr kleinen Blöcken zu lesen. Wenn man da mal einen Bereich von 30 – 40 MB hat der zerschossen ist, dauert das Auslesen mal locker 24h und mehr. Wenn man aber bereit ist auch etwas mehr Verlust in Kauf zu nehmen aber die Datei dafür in absehbarer Zeit da zu haben ist man aufgeschmissen.

Jedoch nicht als Programmierer. Also habe ich mir schnell ein kleines Programm in PHP geschrieben, welches eine Datei in 1 MB Blöcken kopiert und bei Fehlern diese einfach mit 0×00 Zeichen auffüllt. Ich poste es einfach mal, falls es jemand einmal brauchen kann ;)

/**
 * Eine Datei kopieren und kaputte Sektoren überspringen
 */
 
 
define('BLOCKSIZE', 1024*1024); // 1 MB
 
 
if (count($argv) != 3) {
    showHelp();
}
else {
    copyFile($argv[1], $argv[2]);
}
 
function showHelp()
{
    echo "COPYIGNORE FROM TO\n";
}
 
function copyFile($from, $to)
{
    echo "Kopiere: $from => $to\n";
 
    if (!file_exists($from)) {
        die("$from nicht gefunden");
    }
 
    $handleFrom = fopen($from, "rb") or die ("$from konnte nicht geöffnet werden.");
    $handleTo = fopen($to, "wb") or die ("$to konnte nicht geöffnet werden.");
 
    $size = filesize($from);
    $c = 0;
    while ($c < $size) {
        $chunkSize = ($size - $c > BLOCKSIZE) ? BLOCKSIZE : $size - $c;
        $data = fread($handleFrom, $chunkSize);
        if ($data !== false) {
 
            fwrite($handleTo, $data);
 
            // Wenn wir einen Lesefehler finden mit 0x00 auffüllen und zum nächsten Block springen
            if (strlen($data) != $chunkSize) {
                fwrite($handleTo, str_pad('', ($chunkSize - strlen($data)), chr(0)));
                fseek($handleFrom, ($c+ $chunkSize)); // positionieren wo er sein sollte
                echo 'x';
            }
            else {
                echo '.';
            }
        }
        else {
            fwrite($handleTo, str_pad('', $chunkSize, chr(0)));
            echo 'x';
        }
        $c += $chunkSize;
    }
 
    fclose($handleFrom);
    fclose($handleTo);
    echo "done.\n";
}
KategorienAllgemein, PHP Tags:

Enums in PHP

8. Oktober 2010 Sven 2 Kommentare

Durch den Artikel von Daniel inspiriert habe ich auch mal etwas mit Enums herumgespielt. Aber eins nach dem anderen…

Einige kennen Enums eventuell schon von Java. Sie sind eine Art objektorientierte Konstanten. Auf diese Weise kann man einer Methode typsichere Konstanten übergeben.

Hier mal ein Beispiel wie das sonst in der Regel aussieht:

class User
{
    public function setStatus($status)
    {
        // ....
    }
}

Der Nachteil ist ganz klar. Man kann jeden beliebigen Status übergeben. Also auch Zahlen oder Arrays. Und hier kommen die Enums ins Spiel. Wir definieren also eine Statusklasse:

class UserStatus extends CustomEnum
{
    const ACTIVE = 1;
    const NOTACTIVE = 2;
    const DELETED = 3;
}

Und damit können wir dem User nun einen OO-Status übergeben:

$user = new User();
$user->setStatus(UserStatus::ACTIVE());

Was mir an diesem Ansatz besonders gefällt ist, daß die Konstante noch als Konstante erkennbar ist und man auch die Unterstützung der IDE nutzen kann. Man muss lediglich die Klammern hinter die Konstante schreiben, da wir dafür eine Funktion verwenden. Auch die Schreibweise ist eher PHP-Style und damit intuitiver.

So dann will ich auch mal die aufgepeppte User-Klasse zeigen:

class User
{
    private $_status = null;
 
    public function setStatus(UserStatus $status)
    {
        $this->_status = $status;
 
        // Das hier ist nur Optional und um zu zeigen wie man den Status
        // auswerten kann
        switch ($status->ordinal()) {
            case UserStatus::ACTIVE()->ordinal():
                echo 'active';
                break;
            case UserStatus::NOTACTIVE()->ordinal():
                echo 'notactive';
                break;
            case UserStatus::DELETED()->ordinal():
                echo 'deleted';
                break;
        }
 
        echo($status->equals(UserStatus::ACTIVE())) ? 'active' : 'other';
    }
}

Die Namen für die Methoden der Enumklasse habe ich dabei der Java-Enumklassen entnommen.

So und nun kommen wir zum spannenden Teil. Wie sieht die CustomEnum Klasse eigentlich aus?

Den Konstruktor setzen wir private da die Klasse nur über die Konstantenmethoden instanziiert werden soll.
Ebenso lesen wir beim Erstellen der Klasse die Konstanten aus, da wir sie dann brauchen.

    private function __construct()
    {
        $rc = new ReflectionClass($this);
        $this->_constants = $rc->getConstants();
    }

Die wichtigste Arbeit geschieht in der magischen Methode __callstatic(). Dort erzeugen wir ein neues Objekt und weisen ihm den Wert der Methode zu, was ja die Konstante repräsentiert.

    public static function __callstatic($method, $args)
    {
        $class = get_called_class();
        $enum = new $class();
 
        return $enum->_set($method);
    }

In der Set-Methode weisen wir den Wert zu (wer hätte das gedacht) und prüfen ob dieser Wert überhaupt gültig ist. Wenn nicht werfen wir eine Exception. Somit fallen fehlende Konstanten auch recht schnell auf.

    private function _set($index)
    {
        $this->_status = strtoupper($index);
        if (!isset($this->_constants[$this->_status])) {
            throw new UnexpectedValueException($this->_status . ' is not a valid value');
        }
        return $this;
    }

Das war auch schon fast die ganze Magie. Die Java-Klasse bietet noch die statische Methode values() welche alle verfügbaren Enumdefinitionen als Liste zurück gibt um darüber zu iterieren. Dafür müssen wir einen kleinen Kunstgriff machen. Wir erzeugen eine Klasse, damit wir alle Konstanten auslesen können. Für jede Konstante erzeugen wir noch ein Objekt und geben diese Liste zurück. Da wir den Namen der Klasse nur in einer Variablen haben, können wir an dieser Stelle nicht die statische Create-Methode nutzen. Deshalb müssen wir den Inhalt der __callstatic() in die __call() kopieren. Das finde ich zwar sehr unschön, aber mir ist keine bessere Lösung eingefallen.

    public static function values()
    {
        $list = array();
        $class = get_called_class();
        $enum = new $class();
 
        foreach ($enum->_constants as $name => $ordinal) {
            $list[] = $enum->$name();
        }
        return $list;
    }
 
    public function __call($method, $args)
    {
        $class = get_called_class();
        $enum = new $class();
 
        return $enum->_set($method);
    }

Die restlichen Methoden sind ja eher trivial. Und hier nun nochmal die gesamte Klasse in voller Schönheit.

abstract class CustomEnum
{
    /**
     * @var array
     */
    private $_constants = null;
 
    /**
     * @var string
     */
    private $_name;
 
    /**
     * Konstruktor soll nicht öffentlich aufgerufen werden.
     */
    private function __construct()
    {
        $rc = new ReflectionClass($this);
        $this->_constants = $rc->getConstants();
    }
 
    /**
     * Quasi der Konstruktor.
     *
     * @param string $method
     * @param mixed $args
     * @return CustomEnum
     */
    public static function __callstatic($method, $args)
    {
        $class = get_called_class();
        $enum = new $class();
 
        return $enum->_set($method);
    }
 
    public function __call($method, $args)
    {
        $class = get_called_class();
        $enum = new $class();
 
        return $enum->_set($method);
    }
 
    public function ordinal()
    {
        return $this->_constants[$this->_status];
    }
 
    public function name()
    {
        return (string) $this->_status;
    }
 
    public static function values()
    {
        $list = array();
        $class = get_called_class();
        $enum = new $class();
 
        foreach ($enum->_constants as $name => $ordinal) {
            $list[] = $enum->$name();
        }
        return $list;
    }
 
    public function __toString()
    {
        return $this->name();
    }
 
    private function _set($index)
    {
        $this->_status = strtoupper($index);
        if (!isset($this->_constants[$this->_status])) {
            throw new UnexpectedValueException($this->_status . ' is not a valid value');
        }
        return $this;
    }
 
    public function equals(CustomEnum $enum)
    {
        return $enum->_status == $this->_status && get_class($enum) == get_class($this);
    }
}

Diese Implementierung funktioniert jedoch nur ab PHP 5.3, weil erst dort die __callstatic() Methode eingeführt wurde.

KategorienPattern, PHP Tags:

“$var === null” vs. “is_null($var)”

10. September 2010 Sven 2 Kommentare

Vor etwa zwei Wochen hat Zend das Zend Framework 1.10.8 Released. Ein interessanter Eintrag aus dem Changelog ist, dass alle is_null durch === null ersetzt wurden.

Das hat mich interessiert und ich habe mal nachgeschaut warum dies geschehen ist, weil ich der Meinung war soviel Unterschied macht das eh nicht.

Hier mal der Beispielcode für einen Benchmark aus dem entsprechenden Issue:

echo '"is_null($var)" vs. "$var === null"' . "\n";
$max = 100000;
$varNull = null;
$varNotNull = true;
 
echo ' is_null($var): ';
$start = microtime(true);
for ($i = 0; $i < $max; $i++) {
    is_null($varNull);
    is_null($varNotNull);
}
$end = microtime(true);
echo($end - $start) . "\n";
 
echo ' $var === null: ';
$start = microtime(true);
for ($i = 0; $i < $max; $i++) {
    ($varNull === null);
    ($varNotNull === null);
}
$end = microtime(true);
echo($end - $start) . "\n";

Das Ergebnis dabei ist:

"is_null($var)" vs. "$var === null"
 is_null($var): 0.162046194077
 $var === null: 0.0550520420074

is_null ist also dreimal so langsam wie === null.

Ich habe den Test mal etwas verändert und habe mir die Frage gestellt, gibt es einen Unterschied zwischen === null und === NULL?

echo '"$var === nul"" vs. "$var === NULL"' . "\n";
$max = 100000;
$varNull = null;
$varNotNull = true;
 
echo ' $var === null: ';
$start = microtime(true);
for ($i = 0; $i < $max; $i++) {
    ($varNull === null);
    ($varNotNull === null);
}
$end = microtime(true);
echo($end - $start) . "\n";
 
echo ' $var === NULL: ';
$start = microtime(true);
for ($i = 0; $i < $max; $i++) {
    ($varNull === NULL);
    ($varNotNull === NULL);
}
$end = microtime(true);
echo($end - $start) . "\n";

Interessanterweise gibt es dabei noch immer einen messbaren Unterschied:

"$var === null" vs. "$var === NULL"
 $var === null: 0.0449469089508
 $var === NULL: 0.0551202297211

Ich habe den Test mehrmals durchlaufen lassen und das Erbenis war immer in etwa diesem Verhältnis.

Was ist eigentlich der Unterschied zwischen null und NULL? NULL ist eine Konstante mit dem Inhalt null. Die schnellste Variante ist also in der Tat === null. Besonders wenn man diese Prüfung in Schleifen durchführt kann sich das also schon etwas bemerkbar machen.

Und es gibt natürlich auch eine Möglichkeit bestehenden Code schnell mal damit zu optimieren:

find library/ -type f -name "*.php" -exec sed -i -r 's#([^\!])is_null\s*\(([^\(\)]*)\)#\1\2 === null#g' {} \;
find library/ -type f -name "*.php" -exec sed -i -r 's#[\!]is_null\s*\(([^\(\)]*)\)#\1 !== null#g' {} \;
KategorienPHP Tags:

Der PHP Gangsta wird 1 Jahr alt

12. Juli 2010 Sven Keine Kommentare

Kaum zu glauben aber es ist erst ein Jahr her, dass Michael Kliewe seinen Blog PHP Gangsta gestartet hat.
Mittlerweile hat er sagenhafte 141 Artikel veröffentlicht und sich damit einen Platz im Kreis der größten deutschen PHP-Blogs erarbeitet. Auch ich verfolge seine Artikel aktiv und finde immer wieder lesenswerte Neuigkeiten. Damit alles gute von dieser Seite aus und wir freuen uns alle auf noch viele weitere Artikel!

KategorienPHP Tags:

Inversion of Control – Teil 2

9. Juli 2010 Sven Keine Kommentare

So da nun auch Teil 2 auf PHP hates me veröffentlicht wurde (danke nochmal an Nils) hier nun auch nochmal die Veröffentlichung in meinem Blog ;)

In meinem ersten Artikel habe ich gezeigt was Inversion of Control (IoC) und Dependency Injection (DI) sind und warum sie so gut sind.

Nachdem ich so viel positive und konstruktive Kritik bekommen habe, habe ich diese natürlich auch in diesen Artikel einfließen lassen. Ich werde nun also am Anfang noch einmal kurz den Unterschied zwischen IoC und DI erklären. Danach werde ich anhand einer einfachen Beispiel-Implementierung zeigen wie DI implementiert werden kann. Im dritten Teil werde ich dann auf die DI von Symfony 2.0 eingehen.

Unterschied zwischen Inversion of Control (IoC) und Dependency Injection (DI)

Bei der Unterscheidung halte ich mich an Martin Fowler.

Wenn man Inversion of Control einsetzt nennt man diese lose gekoppelten Klassen Komponenten und Services. Komponenten sind dabei eher “dummy” Objekte, die Logik beinhalten aber nicht direkt mit anderen Systemen kommunizieren. Deren Aufgabe ist also hauptsächlich die Berechnung von Daten oder Kapselung von Logik. Services interagieren fleissig mit anderen Services oder Komponenten. Inversion of Control bezeichnet dabei einfach nur, dass die Abhängigkeiten nicht fest einprogrammiert sind, sondern von außen eingeimpft werden. IoC beschreibt also einfach das grundsätzliche Paradigma.

Dependency Injection hingegen beschreibt wie diese Abhängigkeiten gesetzt werden. Beim letzten Mal habe ich bereits auf die Möglichkeit hingewiesen, dass diese Abhängigkeiten über Konstruktoren gesetzt werden können oder aber auch über Setter-Methoden.

In einer Präsentation von Fabien Potencier wird noch die Property-Injection genannt. Dies ist ganz einfach

$myClass = new MyClass();
$myClass->logger = $logger;

Kann man machen muss man aber nicht. Vor allem sollte man das gar nicht. Öffentliche Eigenschaften sind im Sinne der OO sehr unschön und führen zu vielen Seiteneffekten. Also vergessen wir das auch mal ganz schnell wieder.

Eine weitere von Fowler gezeigte Möglichkeit ist die Interface Injection. Hier mal ein kleines Beispiel

interface MyClassInterface {
    function setLogger(Zend_Log_Writer_Abstract $logger);
}

Es soll hierbei mal egal sein ob Zend gut oder schlecht oder besser als etwas anderes ist. Zend_Log_Writer_Abstract ist einfach eine Abstrakte Oberklasse von der es noch weitere konkrete Implementierungen gibt. Da MyClass dieses Interface implementiert bekommt sie automatisch die Abhängigkeit zu diesem Logger gesetzt. MyClass kommt also gar nicht in die Verlegenheit sich selber einen Logger zu suchen.

Interface Injection ist nicht ganz trivial und man muss es sich eine Weile durch den Kopf gehen lassen. Während die anderen Typen der DI eher konkrete Umsetzungen definieren, so definiert Interface Injection eher ein Pattern.

Beispielimplementierung eines Containers zur Verwendung eigener Dependency Injection

Da wir nun wissen, dass es besser ist, dass Abhängigkeiten von außen gesetzt werden wollen wir einmal schauen wie das am besten geht.

Ohne IoC haben wir einfach folgendes gemacht:

$myClass = new MyClass();

MyClass hat sich dann die Datenbankverbindung und den Logger selber geholt. Mit IoC müssten wir so etwas schreiben:

$dbHandle = new Zend_Db_Adapter_Pdo_Mysql(array(
    'host'     => '127.0.0.1',
    'username' => 'webuser',
    'password' => 'xxxxxxxx',
    'dbname'   => 'test'
));
$logger = new Zend_Log();
$myClass = new MyClass();
 
$myClass->setDbHandle($dbHandle);
$myClass->setLogger($logger);

Und das an allen Stellen im Code wo wir MyClass verwenden wollen. Nicht sehr schön und so bringt das auch keine Vorteile. Viel schöner wäre es, wenn es eine Instanz geben würde, die weiß wie sie all diese Komponenten zusammen bauen muss. Wo ich einfach nur noch sage: Gib mir eine frische MyClass.

Na dann fangen wir an…

Das Grundkonzept ist, dass man eine Art Registry hat wo man sich seine Objekte abholt. Konkret wird diese Registry als Container bezeichnet.

Zuerst definieren wir einmal wie unsere Applikation aussehen soll:

$classDefinition = array(
    'Action_Admin_Login' => array(),
    'Action_Game_Login' => array(),
 
    'Controller_Model_Admin' => array(),
    'Controller_Rpc' => array(),
 
    'View_Admin_Login' => array(),
    'View_Admin_Main' => array(
        'Controller_Main'),
    'View_Admin_UserGroupList' => array(
        'Controller_UserGroupList'),
    'View_Admin_UserGroupListModify' => array(
        'Controller_UserGroupList'),
    'View_Admin_UserList' => array(
        'Controller_UserList'),
    'View_Admin_UserShow' => array(
        'Controller_UserShow'),
 
    'View_Game_Main' => array()
);

Dies ist ein einfaches Array, wo jede Klasse die wir konfigurieren als Schlüssel definiert wird. Als Wert hinterlegen wir ein Array mit Namen von Objekten die wir in der Klasse setzen wollen. Dies wird ein sehr einfacher Container, deshalb verwenden wir hier Setter-Injection. Ebenso können unsere Klassen nicht mit Parametern konfiguriert werden. Aber es geht hier auch nicht um einen Container für den Produktiveinsatz sondern um das grundlegende Konzept zu demonstrieren.

Wir haben nun also unsere Registry, welche noch recht leer ist:

class Registry
{
 
    public static $classDefinition = array();
 
    public static function getInstance ($className)
    {
         // @todo
    }
 
}

Dann füllen wir mal die getInstance mit Leben…

        array_push(self::$stack, $className);

Wir legen den Klassennamen auf den Stack. Dies ist notwendig da wir ja eventuell komplexere Bäume laden. Ebenso bauen wir einen Hash auf, welche Klassen wir gerade instanziieren um Schleifen zu erkennen und abzubrechen. Wenn wir also eine Schleife erkennen brechen wir ab.

        if (isset(self::$hashStack[$className])) {
            die('Loop in Objektinstanziierung.');
        }
        self::$hashStack[$className] = true;

Damit ein Objekt nicht jedesmal neu erzeugt werden muss cachen wir es in der Klassenvariablen $object. Wenn das Objekt also noch nicht existiert erzeugen wir es.

Dabei laden wir alle Klassen die wir brauchen. Diese laden wir rekursiv über getInstance(). Wir gehen davon aus, dass für alle Objekte entsprechende Setter- und Getter-Methoden existieren und setzen das Objekt darüber. Am Ende cachen wir es lokal.

        if (! isset(self::$objects[$className])) {
 
            $injection = self::getClassDefinition($className);
            if (is_array($injection)) {
                $obj = new $className();
 
                foreach ($injection as $tempClassName) {
                    $setterFunction =
self::getSetterFunctionForClassName($tempClassName);
                    $tempObj = self::getInstance($tempClassName);
                    $obj->$setterFunction($tempObj);
                }
 
                self::$objects[$className] = $obj;
            }
        }

Nun noch die Stacks aufräumen und das fertige Objekt zurück geben.

        unset(self::$hashStack[$className]);
        array_pop(self::$stack);
 
        return self::$objects[$className];

Das war auch schon die Implementierung der getInstance(). Der Vollständigkeithalber hier noch die getSetterFunctionForClassName-Methode. Dabei werden einfach die Unterstriche entfernt und ein “set” davor gehangen.

    private static function getSetterFunctionForClassName ($className) {
        $functionName = str_replace('_', '', $className);
        return 'set' . $functionName;
    }

Ich hoffe damit das Grundlegende Konzept eines Containers verständlich erklärt zu haben. Im dritten und letzten Teil werde ich dann Dependency Injection im Symfony 2.0 Framework vorstellen.

KategorienPattern, PHP Tags:

Inversion of Control

27. Mai 2010 Sven Keine Kommentare

Heute ist mein Artikel Inversion of Control auf PHP hates me erschienen. Danke nochmal an Nils, dass er mir diese Plattform geboten hat um meine Artikel auch einem größeren Publikum zugänglich zu machen. Ansonsten als Futter für die Suchmaschine hier nochmal der komplette Artikel.

Viele haben sicher schon einmal etwas von Inversion of Control (IoC) und
Dependency Injection (DI) gehört. Und sicher auch, dass es ganz toll ist
und es zu den best practices gehört. Nur eine Frage die oft nicht wirklich
beantwortet wird: Wieso ist das so sinnvoll?

Ich versuche hier einmal diese Frage mit einem Beispiel aus dem realen
Leben zu dokumentieren und zu beantworten…

Wir stellen und eine größere mittelständige Firma vor. Sie hat mehrere
Abteilungen die eigenständig arbeiten. Natürlich müssen da auch Dinge
angeschafft werden und das kostet Geld. Entsprechend ihrem Bedarf
bestellen diese Abteilungen also Dinge wie Drucker, Computer oder
Spezialhardware. Die Rechnungen werden in die Buchhaltung gegeben und dann
von dort aus bezahlt.

Das KANN alles super funktionieren. Aber nur dann, wenn diese Abteilungen
auch verantwortungsbewusst mit Geld umgehen und nur Dinge anschaffen, die
sie wirklich benötigen. Außerdem ist es schwer einen
Abteilungsübergreifenden Überblick zu bekommen, welche Hardware vorhanden
ist. Es kann auch passieren, dass mehrere Abteilungen die selbe
Spezialhardware bestellen obwohl sie sich diese eigentlich hätten teilen
können. Auch ist es schwer nachzuvollziehen wo denn das ganze Geld
geblieben ist.

Wie kann man diesen Zustand also optimieren? So wie es in der Praxis oft
gehandhabt wird: Die Abteilungen melden am Anfang eines Jahres ihren
Bedarf an, daraus wird ein Gesamtbedarf ermittelt und ein Jahresbudget
errechnet. Das wird dann angepasst und optimiert und jeder Abteilung ein
Budget, welches sie zur Verfügung haben, mitgeteilt. Größere Anschaffungen
müssen jedoch erst genehmigt werden (damit kann man doppelte Einkäufe
teurer Spezialhardware vermeiden). Und da diese größeren Anschaffungen nur
noch von einer zentralen Stelle vorgenommen werden hat man darüber auch
immer einen Überblick.

Und Dinge die sich so im Alltag bewährt haben machen auch beim
Softwaredesign Sinn. Wieso soll sich eine Klasse Gedanken machen woher sie
ihren Logger bekommt? Oder wo sie einen Zugang zur Datenbank findet? Das
ist gar nicht ihre Aufgabe. Es ist viel sinnvoller einer Klasse einfach
einen Logger oder eine Datenbankverbindung an die Hand zu geben und sie
macht das was sie am besten kann. Ihre eigene Logik zu implementieren.

Soweit die graue Theorie. Ich hoffe ich habe nun alle (oder zumindest
viele) von dem Nutzen der IOC und DI überzeugen können. Denn nun möchte
ich zeigen, wie die Umsetzung dessen in der Praxis aussehen kann.

Hier der bisher unschöne Weg wie eine Klasse Ressourcen verwendet:

class MyClass
{
 
    private $_logger;
    private $_dbHandle;
 
    function __constructor()
    {
        $this->_logger = Zend_Registry::get('LOGGER');
        $this->_dbHandle = Zend_Registry::get('DB');
    }
 
    function doSomething()
    {
        // etwas loggen
        $this->_logger->write('foo');
 
        // Daten aus der Datenbank lesen
        $this->_dbHandle->select('SELECT * FROM bar');
    }
}

Die Klasse holt sich im Konstruktor selber ihre Abhängigkeiten und
verwendet sie dann.

Besser ist folgender Ansatz:

class MyClass
{
    private $_logger;
    private $_dbHandle;
 
    function __constructor()
    {
    }
 
    function setLogger(Zend_Log_Writer_Abstract $logger)
    {
        $this->_logger = $logger;
    }
 
    function setDbHandle(Zend_Db_Adapter_Abstract $dbHandle)
    {
        $this->_dbHandle = $dbHandle;
    }
 
    function doSomething()
    {
        // etwas loggen
        $this->_logger->write('foo');
 
        // Daten aus der Datenbank lesen
        $this->_dbHandle->select('SELECT * FROM bar');
    }
 
}

Die Klasse bekommt ihre Abhängigkeiten über Setter-Funktionen gesetzt. Man
kann an dieser Stelle die Setter natürlich auch zugunsten von
Konstruktor-Parametern ersetzen. Ein offensichtlicher Vorteil ist schon
einmal, dass die Klasse sich nicht überlegen muss WOHER sie den Logger
oder das DB-Handle bekommt. Der Zugriff über eine Registry ist nichts
weiter als eine objektorientierte Form von globalen Variablen. Wenn sich
der Zugriffsbezeichner ändert, müssen alle Klassen angepasst werden. Ebenso
ist der Rückgabewert einer solchen Registry nicht Typsicher. Dieses Problem
haben wir über die Setter-Funktionen gelöst.

Soweit möchte ich hier erst einmal Schluss machen für diesen Artikel. Im
nächsten Artikel möchte ich dann einmal zeigen, wie man sehr einfach diese
Dependency Injection umsetzen kann.

KategorienPattern, PHP Tags:

Manchmal ist static etwas zu static

7. April 2010 Sven Keine Kommentare

Heute hatte ich einen interessanten Effekt in Bezug auf die Vererbung von statischen Klassenvariablen.

Gegeben sei folgende Klasse, welche einige Config-Daten halten soll:

abstract class Config
{
 
    private static $definitions = array();
 
    protected static function addDefinition ($key, $value)
    {
        self::$definitions[$key] = $value;
    }
 
    protected static function getDefinition ($key)
    {
        return self::$definitions[$key];
    }
}

Die Sicherheitsabfragen und Dokumentation habe ich zum einfacheren Verständniss mal entfernt. Dann haben wir zwei weitere Klassen, welche von unserer abstrakten Config klasse erben.

class Config_A extends Config
{
 
    public static function addA ($key, $value)
    {
        self::addDefinition($key, $value);
    }
 
    public static function getA ($key)
    {
        return self::getDefinition($key);
    }
}
 
class Config_B extends Config
{
 
    public static function addB ($key, $value)
    {
        self::addDefinition($key, $value);
    }
 
    public static function getB ($key)
    {
        return self::getDefinition($key);
    }
}

Nun bestücken wir unsere Klassen mal mit Daten:

Config_A::addA ('treiber', 'configA');
Config_B::addB ('treiber', 'configB');
 
echo Config_A::getA ('treiber');

Nun würde man erwarten, dass auch wieder ein ‘configA’ heraus kommt. Da die statische Klassenvariable jedoch in der abstrakten Oberklasse liegt verwenden beide Unterklassen die selbe Variable. Die Ausgabe lautet nämlich: ‘configB’.

Wie kann man das Dilema also Lösen? Mein erster Ansatz war: Wir verschieben die Variable in die Unterklassen.

abstract class Config
{
 
    protected static function addDefinition ($key, $value)
    {
        $definitions = self::getDefinitions();
        $definitions[$key] = $value;
    }
 
    protected static function getDefinition ($key)
    {
        $definitions = self::getDefinitions();
        return $definitions[$key];
    }
 
    abstract protected static function getDefinitions ();
}
 
class Config_A extends Config
{
    private static $definitions = array();
 
    protected static function getDefinitions()
    {
        return self::$definitions;
    }
 
    ...
}

Analog dazu wird Config_B implementiert.

Jedoch sind abstrakte statische Methoden nur in PHP 5.0.x und 5.1.x erlaubt. Eine weitere Lösung wäre es, aus den Klassen einfach Instanzklassen zu machen. Bei meinem Framework mag ich es jedoch lieber nicht so viele Objekte zum Konfigurieren umher zu jonglieren sondern einfach den Zustand des Systems mit einer statischen Klasse zu verändern.

Letztendlich habe ich folgende Lösung umgesetzt:

abstract class Config
{
 
    protected static $definitions = array();
 
    protected static function addDefinition ($class, $key, $value)
    {
        self::validateDefinitions($class);
        self::$definitions[$class][$key] = $value;
    }
 
    protected static function getDefinition ($class, $key)
    {
        self::validateDefinitions($class);
        return self::$definitions[$class][$key];
    }
 
    private static function validateDefinitions ($class)
    {
        if (! isset(self::$definitions[$class])) {
            self::$definitions[$class] = array();
        }
    }
 
}
 
class Config_A extends Config
{
 
    public static function addA ($key, $value)
    {
        self::addDefinition(__CLASS__, $key, $value);
    }
 
    public static function getA ($key)
    {
        return self::getDefinition(__CLASS__, $key);
    }
 
}
KategorienPHP Tags: