Hallo Andreas,
Plugins (oder Module, wie du es nennst) sind Teile der Aussenwelt. Ja? Ich kenne Plugins eigentlich nur vom IE. Das sind dann ganz andere Programme die von ganz anderen Firmen programmiert werden und in die Oberfläche des IE eingebunden sind.
Ehm, das ist eine Art Plugins fuer den IE.
Meine Module stellen eher Erweiterungen das Basisprogramms dar,
Na und? Sie sind externe Programm-Teile. Es ist nicht zwingend gegeben, dass du sie schreibst.
Tja, aber die Datenquelle ist nicht alles. Z. B. sind Variablen-Inhalte meist durch den Programmablauf bestimmt. ich mache das zur Zeit so, dass ich global eine Datei in alle Scripte einbeziehe, in der die DB-Verbindung hergestellt wird, die Funktionen einmalig stehen, die ich dann in den Scripten nutze. Noch ein paar .ini Änderungen, sonst nichts. Ist das schon eine Schnittstelle?
Ja, allerdings eine relativ primitive. Damit forderst du Inkonsistenzen geradezu heraus.
Dann mach doch hier einen Punkt, an dem sich die Module einhaken koennen. Jedes Modul, dass geladen wird, hat z. B. eine Funktion/Methode 'menu' und gibt eine Struktur zurueck, die die Menue-Struktur des Plugins beschreibt. Der Menue-Teil macht dann daraus das HTML. Aber wie sol ich mir das praktisch vorstellen? Wenn ich auf die Funktion "menu" zugreifen will muß ich die Datei mit include einbinden - dann darf es aber auch nur nur ein Modul geben da ein Funktionsname nur einmal existieren darf!
Dann benutze Klassen und Objekte. Oder setze den Funktions-Namen aus Modul-Name.'_menu' zusammen. Oder mach es sonstwie :) Du weisst doch, wie Referenzen in PHP funktionieren.
Und warum soll ich das als Funktion speichern?
Damit du dort ausfuehrbaren Code hast.
Wäre es nicht besser als mehrdimensionaler Array? Also nur eine Funktion "menu" ins Hauptprogramm, und in den Modul-Dateien dann:
menu[]=array(menü-struktur...);
Klar, koenntest du machen. Damit machst du dich aber von dem Array abhaengig. Was ist, wenn du den mal umbenennst? Ausserdem widerspricht es dem Prinzip der Datentrennung. Ein externes Code-Stueck hat an internen Variablen einfach nix zu suchen, basta.
Wäre es denn nicht besser die komplette Menüstruktur in eine DB-Tabelle oder eigene conf_datei zu schreiben, und dann bei der Installation des Moduls eine neue PHP Datei zu erstellen, die den kompletten, aktuellen Menü-Array hardcoded enthält?
Ansichtssache. Ich halte das fuer zu inflexibel. Ich moechte meine Menues dynamisch aendern, abhaengig vom Programm-Ablauf.
Alles, was Funktionalitaet in sich birgt, muss dynamisch geschehen. Alles, was mit dem Design zusammenhaengt, kann statisch sein. Das hängt aber alles sehr zusammen,
Dann hast du etwas falsch gemacht.
was ist bei einem Kontakt-Formular, das enthält eine Funktionalität, das menü wird dynamsich eingebunden, was solte hier noch dynamisch sein?
Das Kontakt-Formular selber ist ein Modul. Das Modul hat ein Template, das bei Bedarf angepasst werden kann. Das Template bindet das Menue per include() oder etwas aehnlichem ein. Wo ist das Problem?
Mein Problem st, das ich mir jetzt außer dem Menü keine weiteren Möglichkeiten vorstellen kann wo noch etwas dynamisch geschehen müßte.
Dann mach erstmal nur das Menue dynamisch.
Da gibts verschiedene Strategien. Einige lesen ein Verzeichnis aus, in dem alle Module liegen muessen, anderen arbeiten mit Konfigurations-Dateien. Ich halte zweiteres fuer sinnvoller. Also eine eigene Konfigurations-Datei für alle Einstellungen, die durch ein Modul beeinflußt werden können, nur was steht dann darin?
Nee. Eine Konfigurations-Datei, in der die Plugins eingebunden werden. Bei meiner Groupware sieht das z. B. so aus:
LOAD usermanagement.inc.php:UserManagement
Und woher weiß ich vorher wie die Funktion heißen wird?
Entweder, du gibst das vor (das ist gar nicht so selten, das wird sehr oft gemacht wie denn praktisch? bei 2 Modulen: menu1() und menu2()?
Siehe oben.
Und woher weiß ich dann wieviele Module das sind...?
Zaehlen? :)
Bei OO waere das ein typisches Konstrukt fuer Vererbung: die Elternklasse wuerde die Schnittstelle in Form einer abstrakten Klassen darstellen und die Kind-Klasse wuerde sie implementieren Was soll ich hier vererben? Das Problem ist ja an anderer Stelle, ich muß erstmal wissen welche Module ich habe und mit der Information z.B. die Menü-Struktur ermitteln.
Nu haeng dich doch nicht so an der Menue-Struktur auf.
Nur ich weiß weder wie ich an diese beiden Informationen kommen soll, noch was das ganze mit Klassen zu tun haben soll.
Du hast eine Basis-Klasse, nennen wir sie mal 'KorthausPlugin'. Die definiert verschiedene Methoden, unter anderem z. B. die Methode 'menu_hook'. Rueckgabewert und Parameter sind definiert. Nun gehst du, wenn du das Menue generierst, alle in der Konfigurations-Datei angegebenen Plugins durch, instanzierst sie und rufst dann die Methode menu_hook auf. Aus den so gewonnen Funktionen kannst du dann Problemlos das Menue generieren.
oder du hast eine Konfigurations-Variable, in der Referenzen gespeichert sind. Referenzen worauf?
Auf Funktionen.
Und wo bekommt die Variable diese Informationen her?
Diese Variable steht im Modul. Du traegst die Informationen dort ein.
Nein! Bitte nicht :) Aus so einem "Konzept" enstehen die ekeligsten Codes. Ich werde das vermutlich dann kompleztt neu programmieren und nur Teile übernehmen, und dann unter anderen Gesichtspunkten(Erweiterbarkeit, Pflege...) den neuen Code schreiben.
Das ist deine Sache, aber ich wuerde das nicht tun. Ueberfluessiger Arbeitsaufwand...
Ich kann dir, wenn du moechtest, mal ein Konzept fuer eine PHP-basierte Groupware, die ich gerade schreiben muss, darlegen. Mach ich jetzt aber so nicht ungefragt, das gibt ein laengeres Posting... Sehr, sehr gerne! Bin immer an gutem Code sehr interessiert!
Code? :) Ich wollte das Konzept darstellen.
Gut, zunaechst: was ist eine Groupware? Tja, schwer zu sagen. Wieder so ein schwammiger Begriff. Eine Groupware ist letztenendes alles und nichts. Und genau da liegt die Loesung: eine Groupware ist nicht ein festes Software-Paket, sondern viel mehr ein Rahmenwerk, das grundsaetzliche Techniken und Schnittstellen zur Verfuegung stellt. Klar sind in jeder Groupware gewissen "Standard-Plugins" mitgeliefert, aber das Wichtigste ist die leichte Erweiterbarkeit und das leichte Abspecken. Was also muss dieses Rahmenwerk leisten koennen? Nun, es muss grundsaetzlich erstmal einen Zugriff auf Daten erlauben. Der kann auf unterschiedliche Art und Weise passieren: generisch oder abstrahiert. Eine Abstraktions-Ebene einzufuehren waere zwar sauberer, aber dann muesste man 'Meta-Datenstrukturen' einfuehren. Und die haben einen ganz entscheidenden Nachteil: sie sind langsam. Also muss der Zugriff auf die Daten generisch sein, damit jedes Modul seine eigenen Datenstrukturen mitbringen kann. Definiert und bekannt sollten nur die wichtigsten sein: User-, Gruppen- und Rechte-Strukturen. Aber genau so darf ein Plugin auf diese Kern-Daten auch nicht direkt zugreifen. Die Module muessen sich darauf verlassen koennen, dass die Schnittstelle gleich bleibt, also muss noch eine Aufgabe in das Rahmenwerk: der Zugriff auf die Kern-Daten muss abstrahiert werden. Gut, weiter. Der Zugriff auf Plugins muss gegeben sein. Es kann durchaus vorkommen, dass Plugins miteinander kommunizieren muessen. Das ist zwar optional zu halten, aber es muss moeglich sein. Also muss der Zugriff auf Plugin-Routinen abstrahiert werden. Beispiel: ein Webmail-Tool koennte mit dem Adressbuch-Tool arbeiten, um E-Mail-Adressen herauszusuchen. Es muss aber sichergestellt werden, dass diese Zusammenarbeit optional ist. Denn ob ein Modul vorhanden ist oder nicht, haengt von der Konfiguration ab.
So, jetzt geh ich erstmal baden :) Da bin ich wieder.
So. Der naechste Punkt geht schon etwas tiefer. Wir haben gesagt, Plugins muessen miteinander kommunizieren koennen. Was ist aber, wenn ein Plugin Eingabedaten vom User braucht? Prinzipiell muesste also ein Plugin, dass mit einem anderen kommuniziert, die GUIs des anderen nachbauen. Das ist aber nicht sinnvoll, also muss es eine Moeglichkeit geben, die Kontrolle ueber mehrere Requests hinweg an ein Plugin zu geben. Nach den Ablaeufen muss die Kontrolle wieder zurueck gegeben werden an das 'alte' Plugin.
Der letzte Fall ist ein Sonderfall. Ich habe die Mehrsprachigkeit mit in die API aufgenommen, damit das Handling einfacher wird. Ich habe eine Methode aufgenommen, die den Zugriff auf sprachspezifische Daten zulaesst und die aktuelle Sprache zurueck gibt.
Damit stuende zumindest schonmal die Funktionalitaet der API (des Core-Teils) fest. Jetzt schauen wir uns die Schnittstellen mal naeher an:
Der Zugriff auf die Daten geschieht ueber die Methode 'db_query'. Der wird als Parameter die Query als String uebergeben und dazu, optional, der Datenbank-Name. Wird kein Datenbanks-Name uebergeben, so wird die Default-Datenbank genommen. Jede Datenbank muss vorher in der Konfigurations-Datei dem System bekannt gemacht werden. Besteht eine Verbindung zu dem Datenbank-Server, so wird (bei Bedarf) nur die Datenbank gewechselt und dann die Query ausgefuehrt. Besteht keine Verbindung, so wird eine neue Verbindung aufgebaut und dann die Query abgesetzt. Die Rueckgabe ist eine DBResult-Klasse. Doch zu der spaeter. Zusaetzlich zu db_query() gibt es noch db_escape(). Da wir halbwegs datenbanksunabhaengig bleiben wollen, ist das notwendig (verschiedene Datenbanksysteme habe verschiedene Quoting-Regeln). Als Parameter erwartet die Methode den String und, optional, den Datenbanks-Namen. Wird kein Datenbankname uebergeben, so wird die Default-Datenbank benutzt. Die Rueckgabe ist der escaped String.
Najut. Der Zugriff auf die Kern-Daten. Was genau muessen wir ueber den User wissen? Eigentlich nur den Usernamen und vielleicht noch die Sprache. Ach ja, die User-ID waere noch ganz angenehm. Dazu noch, welche Rechte der User hat... gut, also gibt es eine Methode 'user_infos', die einen Array zurueck gibt, dessen erstes Argument die User-ID ist und dessen zweites Argument der Username ist. Die Sprache kann ueber die Methode 'lang' abgefragt werden. Die Rueckgabe ist die aktuelle User-Sprache. Ueber die Rechte bliebe etwas mehr zu sagen. Jedes Plugin muss seine eigenen Rechte mitbringen, aber das Minimum-Recht muss das Recht der generellen Benutzung sein. Warum, wird spaeter klar. Das Abfragen der Rechte muss allerdings durch das Rahmenwerk geschehen. Also gibt es eine Methode 'may_user', die ueberprueft, ob das Recht fuer den aktuellen User gesetzt ist. Um der Problematik der doppelten Benennung zu umgehen, hat jedes Modul seinen eigenen Namespace. Deshalb erwartet diese Methode als ersten Parameter den Rechte-Namen und als zweiten Parameter den Plugin-Namen. Die Rueckgabe ist True, wenn das Recht gesetzt ist, und False, wenn das Recht nicht gesetzt ist.
Der Zugriff auf Plugins ist ueber die Methode 'plugin' gegeben. Diese Methode stellt zwei Sachen sicher: erstens, dass alle Plugins Singletons sind (sprich, sie koennen nur einmal instanziert werden) und b) dass der Zugriff auf Plugins nicht direkt geschieht. Als Parameter erwartet sie den Namen des Plugins. Der Rueckgabewert ist ein Plugin-Objekt, wenn das Plugin existiert und NULL, wenn das Plugin nicht existiert.
Die Kontroll-Verwaltung geschieht ueber die Methoden 'give_control' und 'return_from_control'. Die erste Methode uebergibt die Kontrolle Zeitweise an ein Plugin und erwartet den Plugin-Namen als Parameter. Sie gibt 'true' zurueck, wenn die Kontroll-Uebergabe erfolgreich war und 'false', wenn sie nicht erfolgreich war. Die zweite Methode gibt die Kontrolle an das vorherige Plugin zurueck. Ist kein Plugin mehr im Stapel, wird auf die Hauptseite zurueck geleitet.
Gut, zur Mehrsprachigkeit. Alle Ausgaben, die nicht in die Templates ausgelagert wurden (werden konnten), stehen in einem Array von Strings. In der ersten Ebene ist der Key die Sprache, in der zweiten Ebene ist der Key der Plugin-Name. Innerhalb der dritten Ebene kann dann das Plugin machen, was es will. Zum Zugriff auf diesen Array gibt es die Methode 'strings', dass den Plugin-Spezifischen Teil-Array zurueck gibt. Die Mehrsprachigkeit der Templates wird ueber die Struktur der Template-Verzeichnisse festgelegt. Unterhalb von de/ z. B. befinden sich alle deutschen Templates, unterhalb von en/ befinden sich alle englischen Templates. Die aktuelle Sprache kann ueber die Methode 'lang' ermittelt werden.
Gut, damit haetten wir die generelle API. Dann mal was zu den Plugins selber. Es gibt zwei Arten von Plugins: Applikations-Plugins und 'Snipplets'. Snipplets sind Plugins, die bei jedem Request instanziert werden und ausgefuehrt werden. Das ist z. B. fuer dynamische Inhalte wie News oder so gedacht. Der Haken (der Ausfuehrungspunkt) ist vor jeglicher Ausgabe, direkt nach der Autentifizierung. Die zweite Art von Plugins bezeichnet erweiternde Funktionalitaeten. Ein Beispiel waere hier Webmail oder auch das Adressbuch. Fuer diese Art von Plugin gibt es vier Haken. Ein Haken ist fuer die Hauptseite. Wenn die dargestellt wird, kann es uU userspezifische Meldungen geben (etwa 'Sie haben 5 neue Mails' oder 'Sie haben heute 5 Termine'). Wenn also die Hauptseite dargestellt werden soll, wird das Plugin instanziert und die Methode 'mainpage_hook' wird aufgerufen. Es werden keine Parameter uebergeben. Die Methode ist 'void'. Der naechste Haken ist zur Generierung des Menues. Wenn das Menue generiert werden soll, werden alle Applikations-Plugins instanziert und die Methode 'menu_hook' wird aufgerufen. Es werden keine Parameter uebergeben und die Methode gibt einen Array zurueck, der die Menue-Eintraege beschreibt. Der naechste Haken ist zum erfahren der Rechte, die es fuer dieses Plugin gibt. Wie gesagt, ein Plugin kann beliebige Rechte definieren. Wie die ausgewertet werden, bleibt dem Plugin ueberlassen. Einziges MUST-Recht ist das 'USE'-Recht. Um diese Rechte zu erfahren, wird die Methode 'right_infos' aufgerufen. Es werden keine Parameter uebergeben und als Rueckgabe wird ein Array erwartet, der die Rechte beschreibt. Der letzte Haken ist der, wenn das Plugin gerade die Kontrolle hat. Kontrolle heisst, es werden mehrere Requests nach der Authentifizierung direkt an das Plugin weitergeleitet. Diese Methode nimmt keine Parameter entgegen und ist gibt nichts zurueck.
Jetzt fehlt allerdings noch etwas: der Zugriff auf die API. Der ist dadurch gegeben, dass den Konstruktor des Plugins eine Referenz des API-Objekts und eine Referenz auf das Template-Objekt uebergeben werden. Damit hat ein Plugin beliebigen Zugriff auf Templates (Variablen setzen, Template-Dateien anzeigen lassen) und die API.
Puh. Jetzt hab ich sicher die Haelfte vergessen... mal nachschauen. Ach ja, die DBResult-Klasse. Diese Klasse hat 'nur' die Methoden 'numRows', 'fetchRow', 'isError', 'getMessage', 'free' und 'insertId'. Duerfte selbsterklaerend sein.
Mit dem oben abgebildeten Konzept sollte man eigentlich so ziemlich alles abbilden und einbauen koennen. Mir ist kein Fall eingefallen, der nicht moeglich ist. Jedes Plugin hat seinen eigenen Namespace und jedes Plugin hat seinen eigenen Tablespace. Kollisionen sind also nicht unmoeglich, aber sehr unwahrscheinlich.
Jetzt faellt mir aber auch nicht mehr viel ein.
Gruesse, CK