Fluent Interfaces und der PHP-Weg
Bereits vor einiger Zeit bin ich auf Fluent Interfaces von Martin Fowler gestoßen. Es ist ein einfaches Prinzip mit dem man sich ein wenig Schreibarbeit ersparen kann. Nur zur Information erkläre ich das Prinzip auf die Schnelle:
class Foo
{
public function doSomething ()
{
// do something
}
public function doAnotherThing ()
{
// do another thing
}
public function doLastThing ()
{
// do the last thing
}
}
$foo = new Foo();
// Zum Initialisieren
$foo->doSomething();
$foo->doAnotherThing();
$foo->doLastThing();
Das ist die althergebrachte Weise zum Initialisieren. Natürlich könnte man Selbiges auch im Konstruktor tun, aber das ist für dieses Beispiel irrelevant. Hier das Beispiel mit Fluent Interfaces.
class Foo
{
public function doSomething ()
{
// do something
return $this;
}
public function doAnotherThing ()
{
// do another thing
return $this;
}
public function doLastThing ()
{
// do the last thing
return $this;
}
}
$foo = new Foo();
// Zum Initialisieren
$foo->doSomething()
->doAnotherThing()
->doLastThing();
Es wird also lediglich die eigene Instanz zurückgegeben. Dies
verhindert zwar, dass man einen anderen Wert zurückgeben kann. Aber bei
vielen Operationen ist dies nicht notwendig. Zu häufig gibt es
void-Rückgabewerte. Und gerade für diese Fälle sind Fluent Interfaces absolut geeignet.
Wenn man sich bereits an die Herangehensweise gewöhnt hat, will man es
immer wiederverwenden. So geht es mir zumindest. Besonders beim Setzen
von Variablen ist es mir letztens aufgefallen, dass ich den Drang habe,
es auch bei Variablen zu nutzen.
Leider funktionieren Konstrukte wie $foo->bar = 'bar'->foo = 'foo';
leider nicht. Deshalb muss man das Setzen von Variablen wohl über eine
Methode machen. Viele behaupten, dass man das Setzen und Holen von
Werten eines Objekts sowieso über sogenannte getter- und
setter-Methoden machen sollte, was ich per se nicht verneinen will. In
den meisten Fällen ist es der richtige Weg. Man ist flexibel und kann
sehr schnell etliche Algorithmen verändern, aber das soll nicht das
Thema dieses Blogeintrags sein.
Im PHP-way werden auch solche getter- und setter-Methoden verwendet. Aber es gibt magische Methoden zum Überladen von Variablen, get und set
sind dabei die wichtigsten. Diese sind in den meisten Fällen genauso
sinnvoll zu verwenden. Anpassen kann man es auch sehr einfach. Ein paar
kleinere Beispiele, wie es funktionieren könnte:
Erstmal ein Beispiel mit einigen Variablen, die komplett ohne zugehörigen Algorithmus gesetzt werden können.
class Foo
{
private $_content = array(
'foo' => '',
'foobar' => '',
'bar' => ''
);
public function __set ($name, $content)
{
if(!isset($this->_content[$name]))
{
throw new Exception ('Variable "' . $name . '" not set');
}
$this->_content[$name] = $content;
}
}
Jetzt ein Beispiel, das eine kleine Anzahl von Variablen setzen kann, die jedoch teilweise einen Algorithmus benötigen.
class Foo
{
private $_content = array(
'value' => '',
'square' => '',
'cubic' => ''
);
public function __set ($name, $content)
{
// Überprüfung aus Gründen der Kürze entfernt
switch ($name)
{
case 'square':
$content *= $content;
break;
case 'cubic':
$content *= $content*$content;
break;
}
$this->_content[$name] = $content;
}
}
Und nun ein Beispiel, in dem viele Variablen mit unterschiedlichen Algorithmen gesetzt werden sollen. Der Ansatz basiert auf der Annahme, dass eine bestimmte Konvention für zugehörige setter-Methoden eingehalten werden. In diesem Fall ist es notwendig, dass es für jede Variable eine zugehörige Methode mit dem Präfix "set" gibt. Der danach folgende Teil ist der Name der Variable, die jedoch im Methodennamen mit einerm Großbuchstaben beginnt.
class Foo
{
private $_content = array(
'value' => '',
'square' => '',
'cubic' => ''
);
public function __set ($name, $content)
{
// Überprüfung aus Gründen der Kürze entfernt
$this->{'set' . ucfirst($name)}($content);
}
public function setValue ($value) { ... }
public function setSquare ($value) { ... }
public function setCubic ($value) { ... }
}
Somit kann eine Vielzahl von Variablen ganz intuitiv gesetzt werden.
Einfach $obj->value = '2' o.Ä. Viele bezeichnen so was als den PHP-Weg.
Leider lässt sich der PHP-Weg kaum mit Fluent Interfaces
vereinbaren, da Variablenzuweisungen kein Objekt zurückgeben können.
Deshalb schlage ich einfach vor, eine weitere Methode zu implementieren:
class Foo
{
// Rest bleibt
public function set ($name, $value)
{
$this->$name = $value;
return $this;
}
}
So kann man beides unter einen Hut bringen. Es ist möglich, sowohl Fluent Interfaces zu benutzen, als auch den PHP-Weg zu beschreiten. Beim Setzen einer Variable kann man den intuitiven Weg gehen und beim Setzen einer Vielzahl solcher, kann man Fluent Interfaces benutzen.
$foo = new Foo();
$foo->square = 2;
$foo->set('value', 10)
->set('square', 5)
->set('cubic', 3);
Im Übrigen ist die Methode set nicht notwendig, da man direkt __set aufrufen kann, aber das ist meines Erachtens eine Zweckentfremdung, die nicht notwendig ist.
Ich hoffe, ich konnte Widersprüchlichkeit zwischen Fluent Interfaces und dem PHP-Weg ein wenig reduzieren, sodass man beide Ansätze effektiv und parallel einsetzen kann.
Philipp schreibt:
Hi,
ich habe mich im Zuge meiner Bachelorarbeit mit dem Thema beschäftigt.
Klar sind Fluent Interfaces kein Allheilmittel aber sie machen vor allen Dingen da Sinn wo man eine API anbieten will die sich so gut wie von allein erklärt und den Benutzer davor bewahrt sie falsch zu benutzen. Aber das Erstellen eines Fluent Interface ist nicht unbedingt trivial. Und genau hier setzt meine Bachelorarbeit an. Ich habe versucht das Erstellen von Fluent Interfaces so weit wie möglich zu erleichtern. Mit Hilfe eines Modellers ist es nun möglich Fluent Interfaces in Diagrammen zu modellieren aus denen dann direkt der Code generiert werden kann.
Schau mal einfach unter: http://www.fluent-interfaces.com
Zwar wird zur Zeit Java-Code generiert aber das ganze ist so konzipiert, dass man schnell auch auf PHP-Code erweitern kann.
Grüße
Philipp
Blackflash schreibt:
Hallo Philipp.
Vielen Dank für deinen Hinweis. Daraus hat sich eine Perspektive ergeben, die mir bis dato nicht bewusst war. Habe deine Webseite sowie den Heise-Artikel, den ich auf der Seite gefunden habe, als Lesezeichen hinzugefügt. Werde es mir bei Gelegenheit ein wenig genauer anschauen.
gez. Andre