Der Klasse neue Kleider: Traits
Gastartikel von Thomas Worm.
Ich bin 23 Jahre alt und Verbundstudent bei der DATEV eG. Bereits seit Schulzeiten befasse ich mich intensiv mit der Webentwicklung in PHP. Der Erfahrungsschwerpunkt hierbei liegt auf der Programmierung barrierefreier Portalsysteme, die auf Typo3 aufsetzen (wie z.B. huerdenlos.de).
Einführung
Mit der bald erscheinenden PHP-Version 5.4 wird ein neues Sprachkonstrukt in die PHP-Welt Einzug halten, die Traits. Grob gesagt lässt sich mit diesen eine Methodengruppe definieren, die in verschiedenen Klassen durch eine use-Anweisung eingefügt werden kann. Sicherlich stoßen da nun Einige auf die gleiche Frage wie ich: „Wofür brauch ich das denn bitte? Vererbung ist doch durchaus ausreichend!“
Jain. Mit Vererbung lässt sich viel machen, doch Vererbung ist nicht immer das beste Mittel für wiederverwendbaren Code. Auch folgt PHP dem Java-OOP-Modell, bei dem nur einfache Klassenableitung (single inheritance) möglich ist. Wir können also nur von einer einzigen Vaterklasse erben.
Um Traits zu verstehen, muss man sich klar machen welchen Sinn Vererbung und im Gegensatz dazu Traits haben:
- Bei der Vererbung stehen zwei Klassen in unmittelbarem Zusammenhang, in ihnen fließt sozusagen das selbe Blut. Man kann dies sehr einfach am Menschen betrachten: Es gibt geerbte Eigenschaften und Verhalten, die unmittelbar zu einem Menschen gehören und von den Eltern direkt übernommen werden, wie z.B. Staatsangehörigkeit, Hautfarbe, dass wir sprechen können, usw.. Natürliche kann es hier vorkommen, dass sich auch mal Sachen verändern (Genveränderungen, Umzug in ein anderes Land, …), das sind die Abweichungen, die bei der Vererbung in der Unterklasse modelliert werden.
- Traits sind eher Merkmale, die sich die einzelnen Klassen zulegen. Beispielsweise entscheiden wir selber, ob wir Fußball spielen, Mathematik studiert haben, Turnschuhe tragen oder ähnliches. Die Klasse legt sich also gewisse Eigenschaften und Fähigkeiten zu, man könnte sagen sie kleidet sich ein. Und dabei ist es unrelevant, von welcher Elternklasse sie abstammt. Jede Klasse kann sich mit einem Trait schmücken, auch wenn sie mit einer anderen Klasse, die das Trait verwendet, gar nicht im Zusammenhang steht.
Klingt alles noch sehr theoretisch, nicht? Schauen wir uns das Ganze doch einfach mal an. Was könnte denn ein sinnvolles Merkmal sein, das sich eine Klasse zulegen möchte? Bei Verwendung von Entwurfsmustern (Design Patterns) stößt man öfter auf Funktionalitäten, die Klassen zur Verfügung stellen müssen, um in einem guten Design miteinander zu arbeiten. Da diese Funktionalität eher ein Verhaltensart ist und kein Grundverhalten, das man erbt, empfiehlt es sich hierfür Traits zu nehmen. Ich stelle dies nun am Beobachter- und Singleton-Muster dar:
Das Beobachter-Muster als Trait – Alarmierendes Rot tragen.
Beim Beobachter-Muster geht es darum, dass bestimmte Beobachter-Klassen (Observer Class) eine andere Klasse (Observable Class) im Blick behalten und von ihr über alle Änderungen informiert werden wollen. Hierzu müssen die Beobachter die Informationen bei der beobachteten Klasse abonnieren und wieder abbestellen können (ich verwende hier register() und unregister()). Weiterhin müssen die Beobachter die Informationen entgegennehmen können (ich verwende hier notify()). Es ergeben sich somit folgende Interfaces für Beobachter und beobachtete Klasse:
observer.php – Teil 1:
<?php namespace de\thomasworm\common\observer; interface ObservableInterface { public function register(ObserverInterface $observer); public function unregister(ObserverInterface $observer); } interface ObserverInterface { public function notify(ObservableInterface $sender, $data = null); } //.. ?>
Soweit dürfte das bekannt aussehen. Viele würden jetzt anfangen die Funktionalität von Observable in einer abstrakten Elternklasse zu implementieren und dann nachher davon abzuleiten. Doch nun sind Traits die bessere Wahl:
observer.php – Teil 2:
<?php //... trait ObservableTrait { /** * Array to store observers */ private $_observers = array(); /** * Registers an Observer in Observable * @param ObserverInterface $observer Observer to register */ public function register(/** ObserverInterface */ $observer) { // Only register if not yet registered if (!in_array($observer, $this->_observers, TRUE)) { array_push($this->_observers, $observer); } } /** * Unregisters an Observer in Observable * @param ObserverInterface $observer Observer to unregister */ public function unregister(/** ObserverInterface */ $observer) { $key = array_search($observer, $this->_observers, TRUE); // Only unregister if registered if ($key !== FALSE) { $this->_observers[$key] = null; unset($this->_observers[$key]); } } /** * Notifies all observer * @param mixed $data Optional data to send to observer */ protected function notifyObservers($data = null) { foreach ($this->_observers as $observer) { $observer->notify($this, $data); } } } ?>
Der Trait kümmert sich darum, dass Beobachter abonnieren und abbestellen können und stellt eine intern nutzbare Methode zur Verfügung, mit der alle Beobachter benachrichtigt werden können.
Kleine Anmerkung: ObserverInterface als Type Hint ist bei mir auskommentiert, da es PHP so viel besser gefiel. (Eventuell ein Bug in der von mir verwendeten Version?)
Nun haben wir schon alle Vorbereitungen getroffen, damit wir das Beobachter-Muster anwenden können. Ein kleines Beispiel:
demo.php:
<?php namespace de\thomasworm\samples\observerdemo; require('observer.php'); use de\thomasworm\common\observer\ObservableInterface, de\thomasworm\common\observer\ObserverInterface, de\thomasworm\common\observer\ObservableTrait; class NewsService implements ObservableInterface { use ObservableTrait; public function say($text) { $this->notifyObservers('Hello World!'); $this->notifyObservers($text); } } class MyObserver implements ObserverInterface { private $_name = ''; public function __construct($name) { $this->_name = $name; } public function notify(ObservableInterface $sender, $data = null) { echo ($this->_name . ' notified' . (empty($data) ? '' : ': '.$data) . "\n"); } } $news = new NewsService(); $obs1 = new MyObserver('Observer 1'); $obs2 = new MyObserver('Observer 2'); $news->register($obs1); $news->register($obs2); $news->say('1 + 2'); $news->unregister($obs1); $news->say('2'); ?>
gibt folgende – korrekte – Ausgabe:
Observer 1 notified: Hello World!
Observer 2 notified: Hello World!
Observer 1 notified: 1 + 2
Observer 2 notified: 1 + 2
Observer 2 notified: Hello World!
Observer 2 notified: 2
Für die beobachtete Klasse hätte man auch nur einen Trait statt Interface und Trait verwenden können, allerdings finde ich es korrekter die Anforderungen an die beobachtete Klasse zu beschreiben (und das macht man eben über ein Interface).
Mehrere Traits verwenden – Kleidung zulegen, die einzigartig macht.
Da Entwurfsmuster so schön sind implementieren wir gleich noch eines: Das Singleton-Muster. Beim Singleton-Muster geht es um einfach instanziierte Klassen, das heißt Klassen, von denen in einem Laufzeitsystem nur eine Instanz existieren darf. Typischerweise bekommt eine solche Klasse die Methode getInstance(), welche genau diese Instanz zurück gibt. Es ergibt sich somit folgendes Interface:
singleton.php – Teil 1:
<?php namespace de\thomasworm\common\singleton; interface SingletonInterface { public static function getInstance(); } // ... ?>
Um die Singleton-Implementierung nicht in jeder Klasse einzeln vornehmen zu müssen empfiehlt es sich wieder ein entsprechendes Trait zu implementieren. Was gibt es hierbei zu beachten? Wir wollen, dass nur eine Instanz der Klasse instanziiert wird, das heißt wir müssen den Zugriff auf den Konstruktor sperren, deshalb deklarieren wir diesen als private, so dass er nur noch innerhalb der Klasse selbst erreichbar ist. Auf selbe Weise muss das Klonen der Instanzen unterbunden werden. Die Instanz wird in einer statischen Variable der Klasse gespeichert und getInstance() kümmert sich um die Rückgabe der Instanz bzw. auch deren Erzeugung, falls sie noch nicht existiert. Dieses Verfahren nennt man Lazy Creation, da die Instanz erst bei Bedarf erzeugt wird – PHP darf also bei Programmstart erst einmal faul sein.
singleton.php – Teil 2:
<?php // ... trait SingletonTrait { /** * Stores the only instance of singleton class. */ private static $_instance = NULL; private function __construct() {}; private function __clone() {}; /** * Gets - and if necessary creates - the only instance of singleton class. * @return Object The only instance of singleton class. */ public static function getInstance() { if (self::$_instance === NULL) { self::$_instance = new self; } return self::$_instance;# } } ?>
Wofür ist das nun sinnvoll? Unsere Klasse NewsService könnte ein zentraler Anlaufpunkt für die Datenverteilung sein. Würde jede Programmkomponente eigene Instanzen von NewsService registrieren, so müssten auch ihre Beobachter sich bei jeder Instanz registrieren. Es empfiehlt sich daher den NewsService zu einem Singleton werden zu lassen, hierzu können wir zu den bereits vorhandenen Interfaces das SingletonInterface und zu den vorhandenen Traits das SingletonTrait hinzufügen:
Auszug demo.php
<?php // ... require('singleton.php'); use de\thomasworm\common\singleton\SingletonInterface, de\thomasworm\common\singleton\SingletonTrait; // ... class NewsService implements ObservableInterface, SingletonInterface { use ObservableTrait, SingletonTrait; // ... } // ... ?>
Führt man das Programm nun aus erhält man eine entsprechende Fehlermeldung, da vom Singleton keine Instanz mehr per new erzeugt werden darf:
Fatal error: Call to private de\thomasworm\samples\observerdemo\NewsService::__construct() from invalid context in demo.php on line 33
Der Demo-Code muss also entsprechend angepasst werden. Ich habe hier auch noch eine Variable $news2 eingeführt, damit man sieht, dass es wirklich nur eine einzige Instanz des Singleton NewsService gibt:
Auszug aus demo.php:
<?php // ... // war: $news = new NewsService(); $news = NewsService::getInstance(); $obs1 = new MyObserver('Observer 1'); $obs2 = new MyObserver('Observer 2'); $news->register($obs1); $news->register($obs2); $news->say('1 + 2'); $news2 = NewsService::getInstance(); $news2->unregister($obs1); $news2->say('2'); ?>
Wir erhalten wieder die ursprüngliche Ausgabe.
Trait-Konflikte – Wenn sich die Farbe der Kleidungsstücke beißt.
So wie es Kleidungsstücke gibt, die sich unter einander nicht vertragen, weil die Farbe beißt, gibt es auch Traits, die sich untereinander nicht vertragen, weil sie zum Beispiel gleiche Funktions- oder Variablennamen beinhalten. Auch dies möchte ich noch am Beispiel präsentieren.
Nehmen wir an unsere Implementierung nach dem Observer-Muster soll erweitert werden. Der Beobachter soll sich nun auch merken, welche Klassen er beobachtet, damit es leichter ist – z.B. bei Zerstörung des Beobachters – auch alle Informations-Abonnements aufzuheben. Dazu führen wir einen neuen ObserverTrait ein:
observer.php
<?php // ... trait ObserverTrait { /** * Array to store observables. */ private $_observables = array(); /** * Register me at Observable * @param ObservableInterface $observable Observable to register at */ protected function register(/** ObservableInterface */ $observable) { if (!in_array($observable, $this->_observables, TRUE)) { $observable->register($this); array_push($this->_observables, $observable); } } /** * Unregister me at Observable * @param ObservableInterface $observable Observable to unregister a */ protected function unregister(/** ObservableInterface */ $observable) { $key = array_search($observable, $this->_observables, TRUE); if ($key !== FALSE) { $observable->unregister($this); $this->_observables[$key] = null; unset($this->_observables[$key]); } } /** * Unregister me at all Observables. Should be run when Observer will be destroyed. */ protected function unregisterAll() { foreach ($this->_observables as $observable) { $observable->unregister($this); } unset($this->_observables); } } ?>
Und ein kleines neues Beispiel dazu, in dem eine Sender genannte beobachtete Klasse zwei Empfaenger genannte Beobachter erzeugt werden. Die Empfaenger registrieren sich bei Instanziierung über den Konstruktor beim Sender. Für den Fall, dass wir einen Empfaenger zerstören wollen, bieten wir die Funktion prepareDestroy() an, die alle Registrierungen bei beobachteten Klassen aufhebt:
demo2.php
<?php namespace de\thomasworm\samples\observerdemo; require('observer.php'); use de\thomasworm\common\observer\ObservableInterface, de\thomasworm\common\observer\ObserverInterface, de\thomasworm\common\observer\ObservableTrait, de\thomasworm\common\observer\ObserverTrait; class Sender implements ObservableInterface { use ObservableTrait; public function say($text) { $this->notifyObservers($text); } } class Empfaenger implements ObserverInterface { use ObserverTrait; private $_name = ''; public function __construct($name, $observable) { $this->_name = $name; $this->register($observable); } public function notify(ObservableInterface $sender, $data = null) { echo ($this->_name . ' notified' . (empty($data) ? '' : ': '.$data) . "\n"); } function prepareDestroy() { $this->unregisterAll(); } function __destruct() { echo ($this->_name . ' destroyed!' . "\n"); } } $s = new Sender(); $e1 = new Empfaenger('Observer 1',$s); $e2 = new Empfaenger('Observer 2',$s); $s->say('1 + 2'); $e2->prepareDestroy(); unset($e2); $s->say('1'); ?>
Es erfolgt die gewünschte Ausgabe:
Observer 1 notified: 1 + 2
Observer 2 notified: 1 + 2
Observer 2 destroyed!
Observer 1 notified: 1
Observer 1 destroyed!
Wem sich der Sinn des Traits mit unregisterAll() noch nicht erschließt, der sollte einfach mal den prepareDestroy()-Aufruf aus dem Beispiel löschen. In der Ausgabe wird Observer 2 destroyed! dann erst am Ende zu sehen sein. Warum? PHP zerstört Objekte erst, wenn es keine Referenz mehr auf sie gibt. Da der Sender aber aufgrund der Registrierung noch eine Referenz auf die Empfaenger-Instanz enthält zerstört PHP das Objekt erst zum Programmende. Der Trait stellt für uns also eine Möglichkeit dar die Zerstörbarkeit des Objektes zu bewahren.
Was hat das nun mit Trait-Konflikten zu tun? Bisher nichts, aber jetzt kommt ein schlauer Programmierer, der möchte, dass eine Klasse sowohl Beobachter als auch beobachtet sein kann. Der Sender könnte beispielsweise so abgeändert werden:
Auszug aus demo2.php – verändert:
class Sender implements ObservableInterface, ObserverInterface { use ObservableTrait, ObserverTrait; public function say($text) { $this->notifyObservers($text); } public function notify(ObservableInterface $sender, $data = null) { echo('Habe erhalten... ' . $data . "\n"); } }
Mit einem schönen Schmunzeln – wir können uns ja bereits denken was passiert – erhalten wir beim Ausführen eine Fehlermeldung:
Fatal error: Trait method register has not been applied, because there are collisions with other trait methods on de\thomasworm\samples\observerdemo\Sender in demo2.php on line 21
Was ist passiert? Die zwei verwendete Traits implementieren Methoden unter demselben Namen. PHP kann nun nicht einfach entscheiden, welche Methode es nimmt oder welche Methode es verwirft, wir müssen also selbst Hand anlegen und PHP mitteilen, welche Methoden er verwenden oder umbenennen soll:
Auszug aus demo2.php – verändert:
<?php // .... use ObservableTrait, ObserverTrait { ObservableTrait::register insteadof ObserverTrait; ObservableTrait::unregister insteadof ObserverTrait; ObserverTrait::register as public registerMe; ObserverTrait::unregister as unregisterMe; } // ... ?>
Es gibt verschiedene Möglichkeiten Traits beim Einbinden abzuändern:
- Wahl beim Konflikt:
Bindet man zwei Traits ein, die Methoden mit demselben Namen besitzen, so muss explizit mitgeteilt werden, dass eine Traitmethode an Stelle der anderen verwendet werden soll. Hierfür gibt es das Schlüsselwort insteadof. Erst wenn alle Konflikte auf diese Weise aufgelöst wurden, lassen sich konkurrierende Traits einbinden. Im Beispiel habe ich mich für die Verwendung der ObservableTrait-Methoden unter Originalnamen entschieden, da sich sonst weitere Probleme ergeben würden (Die Observer erwarten ja, dass die beobachtete Klasse diese Methoden entsprechend anbietet, es besteht also Abhängigkeit. Auf die Methoden aus ObserverTrait besteht dagegen keine Abhängigkeit.) - Vergeben eines Alias:
Methoden aus Traits können einen weiteren Alias erhalten. Über diesen Alias kann man auf die Methoden zugreifen, wenn unter dem eigentlichen Methodennamen die Methode eines anderen Traits verwendet wurde. Es handelt sich hierbei wirklich nur um einen Alias, durch das Umbenennen wird der alte Methodennamen nicht aufgegeben, d.h. eine Methode – ohne Konflikt zu anderen Traits – die einen Alias erhält, ist trotzdem unter ihrem ursprünglichen Methodennamen aufrufbar. Der Alias wird über das Schlüsselwort as zugewiesen. Im Beispiel wurden die Methoden aus ObserverTrait mit einem Alias versehen, um Zugriff hierauf zu erhalten (ihr Originalname wurde ja mit den Methoden aus ObservableTrait überlagert). - Verändern der Sichtbarkeit:
Die Sichtbarkeit von Methoden, die durch Traits vorgegeben wird, lässt sich ebenfalls über as abändern. Dies kann in Kombination mit einer Aliasvergabe erfolgen, muss aber nicht. Um auf registerMe() auch außerhalb der Klasse zugreifen zu können wurde im Beispiel die Sichtbarkeit auf public geändert.
Nachdem nun also die Trait-Einbindung abgeändert ist lässt sich der Sender selber als sein eigener Beobachter registrieren:
Auszug demo2.php – verändert:
<?php // ... $s = new Sender(); $s->registerMe($s); // ... ?>
Die Ausgabe ist korrekt:
Habe erhalten... 1 + 2
Observer 1 notified: 1 + 2
Observer 2 notified: 1 + 2
Observer 2 destroyed!
Habe erhalten... 1
Observer 1 notified: 1
Observer 1 destroyed!
Schlussbemerkungen und Fazit
Es gäbe hier noch einiges Mehr zu erwähnen wie zum Beispiel Trait-Einbindung in Traits, was aber den Rahmen sprengen würde. Die Grundlagen sind dargestellt und es zeigt sich, dass Traits ein sehr mächtiges Mittel sind. Trotz der Vorteile, die sie bieten – meiner Meinung nach besonders im Umfeld mit Entwicklungsmustern – sollte man sie wohl bedacht einsetzen. Eine falsche Verwendung wird mit schlecht wartbaren Code bestraft. Getreu dem alten Motto „Komposition vor Vererbung“ gilt auch „Komposition vor Traits“.
Oder anders gesprochen im bildlichen Vergleich: Entsprechender Charme hat beim Flirten mehr Wirkung als die Kleidung, die man an hat. In diesem Sinne: Viel Spaß beim Bepacken des PHP-Kleiderschrankes für Klassen 😉
Geile Scheisse! Sowas hab ich mir schon öfter mal in PHP gewünscht! Danke für diesen Artikel! 🙂
Knotschi
27 Sep 11 at 12:58
Wow, sehr cool. Du bist erst 23??!?
chp
27 Sep 11 at 14:51
Klingt für mich wie eine Mehrfachvererbung.. Wie im Artikel bereits erwähnt, besteht schnell die Gefahr schlecht wartbaren Code zu erhalten.. Wäre es nicht einfacher gewesen die selbe Lösung wie bei C#.net via Interfaces zu benutzen?
Noval
27 Sep 11 at 15:44
Nett, wie man immer wieder versucht mit technischen Gimmicks Probleme zu lösen. Wir haben schon seit langem alle Werkzeuge und das Wissen, praktisch alles (mit OOP oder ohne) lösen zu können.
Aber nein, man benutzt immer noch so böse Dinge wie das Antipattern Singleton und Vererbung (etwas, dass einer der OOP-Väter heutzutage nicht mehr implementieren würde…).
Und mit diesen Traits führt man eine weitere Möglichkeit ein, aus Clean Code einen Wust an Voodoo einzuführen, den kein Mensch braucht.
Danke aber für die Hinweise am Schluß, das sollten sich Entwickler erst mal aneignen, bevor sie sich an fortgeschrittene Themen ran machen.
Ich habe es meinem Team verboten es zu benzuen bzw. nur nach Bewilligung 😉
Don
27 Sep 11 at 15:46
Sorry, aber der Artikel ist wie Typo3. Total überladen und dabei soll es doch nur einen einfachen Sachverhalt klären.
Ich habe jetzt schon mehrere Erklärungen zu Traits gesehen und deine war mit Abstand die umständlichste.
Trotzdem Danke für deine Mühe, und andere bewerten das bestimmt nicht so wie ich 😉
sonyon
27 Sep 11 at 16:02
Es heißt übrigens TYPO3 und nicht Typo3!
Wir
27 Sep 11 at 22:26
@Don: Das ist mir ein bisschen zu puristisch gedacht.
Zunächst mal ist das Singleton kein Antipattern. Es mag sein, dass es zu oft genutzt wird (ob das wirklich so ist, oder nur kolportiert wird …); aber an sich kann es eine sinnvolle Lösung für Probleme sein.
Genauso mit Traits. Jeder Softwarearchitekt kennt die Idee, dass man ein Interface definiert und dabei weiß, dass eine Funktion immer gleich bleibt. Warum sie also nicht in Traits packen?
Diese „reine Schule“ der OOPs und DPs gibt es halt nicht mehr. Sprachen entwickeln sich, Traits sind ein guter Ansatz.
Clean Code bedeutet v.a. die Vermeinung von Redundanzen, es gibt Anwendungsszenarien in denen Traits dafür sinnvoll sind, also ist’s auch eine sinnvolle Erweitertung.
chp
28 Sep 11 at 00:02
„Zunächst mal ist das Singleton kein Antipattern.“
Doch. Punkt. Keine Ausnahmen.
(Siehe auch die Unterscheidung zwischen Singleton [Pattern] und singleton:
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/ )
„…dass man ein Interface definiert und dabei weiß, dass eine Funktion immer gleich bleibt.“
Auch das ist der Sinn eines Interfaces, war das jemals anders?
Nino Martincevic
28 Sep 11 at 08:26
Nur eine Bemerkung am Rande: zu einem „richtigen, saubaren“ Singelton gehört auch private function __sleep() und private function __wakeup().
Agata Raap
28 Sep 11 at 10:08
Hmm, für die meisten Anwendungen scheint mir das Strategy-Pattern weit besser geeignet. Und den Rest könnte man vermutlich mit (notfalls namespacebasierten) Funktionen erschlagen. In Sprachen wie Javascript sehe ich den Sinn für Mixins ja vielleicht noch gegeben, aber in PHP? Ich weiß nicht. Zugegeben – Observer oder ArrayAccess mögen wenige sinnvolle Anwendungen darstellen.
nk
28 Sep 11 at 13:52
[…] Anwendungsfall dafür. Eine richtige Mehrfachvererbung wäre natürlich schöner gewesen, aber mit Traits kann man denke ich auch leben. Meiner Meinung nach ist es (für manche Anwendungsfälle) eine […]
Christophs Blog » PHP 5.4: Traits
30 Sep 11 at 00:32
Hab dazu schon vor einiger Zeit was verfasst, intereissiert vielleicht wen: http://www.kingcrunch.de/blog/2011/08/01/php5-4-traits-aka-horizontal-reuse/
#Singleton: Denke, mittlerweile kann man das gut und gerne zu den Antipattern dazu zählen.
KingCrunch
1 Okt 11 at 02:46
Servus,
schöner Beitrag. Ich habe aber noch eine Anmerkung bzw. Frage: Ich habe neulich mein Singleton (abstract) auf Traits umgestellt, da es mir als logisch erschien. Nun hatte ich gestern einen Benchmark durchgeführt und festgestellt, dass die Trait-Variante um fast 15 Prozent langsamer arbeitet als ein extends.
Habt ihr das auch schon bemerkt?
Sebastian
2 Feb 12 at 16:37
[…] Feature: Traits (auch “Horizontal Reuse” renannt). Hier im Blog gibt es auch schon einen sehr ausführlichen Artikel über Traits.<? class Base { public function sayHello() { echo 'Hello '; } } trait SayWorld { public function […]
PHP 5.4.0 released! Neue Funktionen | PHP Gangsta - Der PHP Blog mit Praxisbezug
6 Jun 12 at 00:28
Hallo,
soweit sieht das ja alles recht nett aus aber ich wollte es mal lokal nach bauen und bin nur bis zur demo.php gekommen, denn bereits das lässt sich nicht nachbilden o.O
Die Namespaces sind ja nur für den Interpreter da und man muss diese Ordnerstruktur ja nicht tatsächlich auch haben soweit ich weiß soll man die Ordner Struktur aber anpassen um die Dateien besser zu finden.
Fehlermeldung:
Fatal error: Declaration of de\thomasworm\common\observer\ObservableTrait::register($observer) must be compatible with de\thomasworm\common\observer\ObservableInterface::register(de\thomasworm\common\observer\ObserverInterface $observer) in D:\xampp\htdocs\99_Bastelstube\php\trait\demo.php on line 17
Stefan
4 Mai 14 at 10:55
Nun wie sich rausgestellt hat fehlen ein paar declarations beim aufrufen der interface funktionen…
Da die Methoden im Interface mit Typ definiert sind, müssen die klassen die das Interface einbinden auch tun und leider wurde das iwie über all weg gelassen, vllt. wirds ja noch angepasst wer weiß.
Stefan
4 Mai 14 at 12:18
Ich beschäftige mich darüber,warum die geschützte Eigenschaften und Methoden gibt,wenn ich sie doch
anderweitigen Möglichkeiten die geschützte inhalte
aufrufen kann.
Gruss
Gast
21 Apr 24 at 17:55