php.de

Zurück   php.de > Webentwicklung > PHP-Fortgeschrittene

PHP-Fortgeschrittene Arbeiten mit PHP ohne Einschränkungen

Antwort
 
LinkBack Themen-Optionen Thema bewerten
Alt 24.04.2006, 11:32  
Erfahrener Benutzer
 
Registriert seit: 21.12.2004
Beiträge: 5.234
PHP-Kenntnisse:
Fortgeschritten
mepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblick
mepeisen eine Nachricht über ICQ schicken mepeisen eine Nachricht über Skype™ schicken
Standard diverse Design-Fragen eines Frameworks

Hallöchen.

Ich habe einmal meine Archive entstaubt und bin dort auf einige Konzepte
meines Framework-Projektes gestossen, was ich mal für PHP 4 gebaut
hatte. Es ging um ein Anwendungs-Framework für PHP. Nun denn, auch
um mal wieder was eigenes zu basteln (ev. später Open Sourced, wer
weiss) werde ich das zugrundeliegende Konzept fitt für die Zukunft
machen (PHP5...).

1. Plugins
Worum gehts? Im ersten Schritt wird ein Grundkonzept für Plugin-
Unterstützung entwickelt. Das ganze soll relativ schnell und banal sein
und trotzdem flexibel.

Die Idee:
Eine zentrale Klasse verwaltet die Plugin-Aufgaben (wie auch sonst).
Plugins werden generell anhand eines eindeutigen Namens identifiziert
(beispielsweise de.eisengardt.plugin.MyDummyPlugin). Jedes Plugin
definiert benötigte und optionale Abhängigkeiten. Erst wenn alle
benötigten Abhängigkeiten, sprich die Plugins dafür, vorhanden sind, lässt
es sich laden. Die optionalen Plugins sind wie der Name sagt optional und
werden geladen, wenn sie da sind, ansonsten werden sie ignoriert.

Ein Plugin definiert zwei Dinge: 1. Eine Methode, die als Listener fungiert
und aufgerufen wird, wenn ein benötigtes oder optionales Plugin zur
Verfügung steht, wenn also eine Abhängigkeit zur Verfügung steht.
2. Eine Methode, die in der Lage ist, ein Interface zu implementieren.

Praktisches Beispiel aus einem Datenbank-Manager: Es gibt ein
allgemeines Plugin, das Datenbankzugriffe managed, nennen wir
es "DBManager" (Vorbild ist ein bischen das JDBC *g*).
1. Das Plugin einer MySQL-Implementierung erhält nun die
Nachricht "DBManager wurde geladen".
2. Das MySQL-Plugin registriert sich am DBManager mit dem Hinweis, alle
Verbindungen vom Typ "MySQL" übernehmen zu können.
3. Ein Plugin, dass den Datenbankzugriff machen will, fordert eine
Datenbank-Verbindung an und übergibt die Konfiguration (dort steht
drin: "MySQL").
4. Der DB-Manager bittet nun das MySQL-Plugin darum, das Interface
AbstractDatabaseConnection zu implementieren und kriegt ein Objekt
zurück, was dann letzlich eine MySQL-Datenbankverbindung repräsentiert.

Vorteil: Es werden nur dann Klassen geladen, wenn dies notwendig ist,
also wenn beispielsweise eine Implementierung eines Interfaces
gewünscht wird.

Das, was die Plugins managt, lädt alle Plugins samt Abhängigkeiten aus
einer Datei. Alternativ kann man ein Plugin direkt aus einem Verzeichnis
laden, was ev. leicht langsamer ist, aber bei der Frage "Wer kam früher,
die Henne oder das Ei" unumgänglich ist. Zu deutsch: Wenn es noch keine
Datei gibt, in der Plugins drinstehen (beispielsweise bei der Installation
oder zur Entwicklung des Frameworks/ eines neuen Plugins) soll es
trotzdem gehen.

Wie funktioniert das aus Sicht eines Scriptes?
PHP-Code:
<?php
require_once('lib/fwk/boot.php');
if ((
$plugin MepCore::LoadPlugin('de.eisengardt.cms')) === false) die('Systemfehler!');
$plugin->TueIrgendwas();
// Alternative:
$website $plugin->GetInterface('ICmsWebsite');
$website->ParseRequest();
?>
Beispiel der Plugin-Klasse:
PHP-Code:
<?php
#!SKIP
############################################################
### Dieses Script ist Teil der Bibliothek de.eisengardt.php5
### This script is part of library de.eisengardt.php5
###
### Es wird ohne schriftliche Genehmigung durch den Copyright-
### Inhaber nicht erlaubt, dieses Script oder Teile daraus
### zu verändern oder in andere Sprachen zu übersetzen. Dieses
### Script darf ausschliesslich mit ausdrücklicher Genehmigung
### durch den Copyright-Inhaber oder durch von ihm berechtigte
### Lizenzinhaber genutzt, installiert, kopiert oder weitergegeben
### werden.
###
### You are not allowed to edit or translate this script or
### parts of this script without authorization by copyright
### owner. You are not allowed to use, install, copy or pass
### this script without owning an authorization by copyright
### owner.
###
### Copyright ® 2004-2006 by Martin Eisengardt
###                          Friedenstrasse 2
###                          76327 Pfinztal
###                          [url]http://www.eisengardt.de[/url]
###                          [email]martin@eisengardt.de[/email]
############################################################
#!END

/*   Script: /lib/core/IMepPlugin.php
 *    Modul: core
 * Funktion: Basis-Interface für eine Plugin-Implementierung
 */

#!REQUIRE{build/config.php}

if (!defined('MEP_CORE_ROOT')) die('Error #0: Core not started...');

/** Basis-Interface f&uuml;r eine Plugin-Implementierung
 * 


 * 
 * Jedes Plugin stellt eine Implementierung dieses Interface bereit,
 * um den Lebenszyklus eines Plugins zu entsprechen. &Uuml;ber ein
 * Script <code>boot.php</code> innerhalb des jeweiligen Plugin-
 * Verzeichnisses wird eine Instanz der Plugin-Klasse angelegt und
 * in die Variable <code>$plugin</code> hinterlegt.


 * 
 * Hat dieses Plugin Abh&auml;ngigkeiten, kann man nicht sichergehen,
 * dass auf diese Abh&auml;ngigkeiten bereits im Konstruktor
 * zur&uuml;ckgegriffen werden kann. Stattdessen wird nach dem
 * Starten dieses Plugins die Methode <code>OnPluginLoaded</code>
 * je geladene Abh&auml;ngigkeit aufgerufen. F&uuml;r optionale
 * Abh&auml;ngigkeiten findet jedoch kein Aufruf statt.
 */
interface IMepPlugin
{
    
    
/** Wird aufgerufen, sobald ein abh&auml;ngiges Plugin geladen wurde
     * 


     * 
     * @param $name String - Name des abh&auml;ngigen Plugins, das nun
     * verf&uuml;gbar ist.
     */
    
public function OnPluginLoaded($name);
    
    
/** Gibt ein Interface f&uuml;r Plugins zur&uuml;ck
     * 


     * 
     * Das aufrufende Plugin hat daf&uuml;r Sorge zu tragen, dass das
     * zu implementierende Interface zur Verf&uuml;gung steht.


     * 
     * @param $class String - Klassenname des Interface
     * 
     * @return Object - Objekt-Instanz, welches das &uuml;bergebene
     * Interface implementiert oder <code>FALSE</code> (Boolean), wenn
     * das Plugin dieses Interface nicht bereitstellen kann.
     */
    
public function GetInterface($class);
    
    
/** Setzt und initialisiert den Arbeitspfad des Plugins.
     * 


     * 
     * Wird nach dem Anlegen des Plugins aufgerufen und vor der
     * Methode <code>OnPluginLoaded()</code>, um den Arbeitspfad
     * des Plugins zu setzen. In diesem Pfad kann das Plugin
     * individuelle Daten ablegen.


     * 
     * @param $path String - absoluter Arbeitspfad des Plugins
     */
    
public function InitializeWorkspace($path);
    
// interface IMepPlugin
?>
Die Klasse, die bei der Installation zu Rate gezogen wird:
PHP-Code:
<?php
#!SKIP
############################################################
### Dieses Script ist Teil der Bibliothek de.eisengardt.php5
### This script is part of library de.eisengardt.php5
###
### Es wird ohne schriftliche Genehmigung durch den Copyright-
### Inhaber nicht erlaubt, dieses Script oder Teile daraus
### zu verändern oder in andere Sprachen zu übersetzen. Dieses
### Script darf ausschliesslich mit ausdrücklicher Genehmigung
### durch den Copyright-Inhaber oder durch von ihm berechtigte
### Lizenzinhaber genutzt, installiert, kopiert oder weitergegeben
### werden.
###
### You are not allowed to edit or translate this script or
### parts of this script without authorization by copyright
### owner. You are not allowed to use, install, copy or pass
### this script without owning an authorization by copyright
### owner.
###
### Copyright ® 2004-2006 by Martin Eisengardt
###                          Friedenstrasse 2
###                          76327 Pfinztal
###                          [url]http://www.eisengardt.de[/url]
###                          [email]martin@eisengardt.de[/email]
############################################################
#!END

/*   Script: /lib/core/IMepPluginInfo.php
 *    Modul: core
 * Funktion: Basis-Interface für eine Plugin-Implementierung
 */

#!REQUIRE{build/config.php}

if (!defined('MEP_CORE_ROOT')) die('Error #0: Core not started...');

/** Basis-Interface f&uuml;r eine Plugin-Implementierung
 * 


 * 
 * Jedes Plugin stellt eine Implementierung dieses Interface bereit,
 * um System-Informationen zum Plugin anzubieten. Unter anderem
 * werden so die Abh&auml;ngigkeiten zwischen mehreren Plugins
 * verwaltet.


 */
interface IMepPluginInfo
{
    
    
/** Gibt eine Information zum Plugin zur&uuml;ck
     * 
/

     * 
     * @param $key String - Datenelement, das zur&uuml;ckgegeben
     * werden soll. Ist eine der Konstanten unterhalb dieser Klasse.
     * 
     * @return String - Information zum Plugin oder <code>FALSE</code>
     * (Boolean), wenn der Key nicht bekannt ist.
     */
    
public function GetData($key);
    
    
/** Gibt die ben&ouml;tigten bzw. abh&auml;ngigen Plugins zur&uuml;ck.
     * 


     * 
     * @return Array[String] - Ein Array aus den Plugin-Namen, die f&uuml;r
     * dieses Plugin ben&ouml;tigt werden. Wenn keine Plugins ben&ouml;tigt
     * werden, muss ein leeres Array zur&uuml;ckgegeben werden.
     */
    
public function GetRequiredDependencies();
    
    
/** Gibt die zus&auml;tzlichen bzw. optionalen Plugins zur&uuml;ck.
     * 


     * 
     * @return Array[String] - Ein Array aus den Plugin-Namen, die f&uuml;r
     * dieses Plugin optional verwendet werden. Wenn keine Plugins ben&ouml;tigt
     * werden, muss ein leeres Array zur&uuml;ckgegeben werden.
     */
    
public function GetAdditionalDependencies();
    
// interface IMepPluginInfo


/** Titel des Plugins */
define('MEP_PI_TITLE',            'MEP_PI_TITLE');
/** Ausf&uuml;hrliche Beschreibung zum Plugin */
define('MEP_PI_DESCRIPTION',    'MEP_PI_DESCRIPTION');
/** Name des Plugin-Autors */
define('MEP_PI_AUTHOR',            'MEP_PI_AUTHOR');
/** Autor-Kontakt */
define('MEP_PI_AUTHOR_EMAIL',    'MEP_PI_AUTHOR_EMAIL');
/** Webseite zum Autor oder zum Plugin */
define('MEP_PI_AUTHOR_URL',        'MEP_PI_AUTHOR_URL');
/** Versionsnummer des Plugins */
define('MEP_PI_VERSION',        'MEP_PI_VERSION');

?>

Da ich wie gesagt plane, das ganze dann ev. auch der Öffentlichkeit
zugänglich zu machen (vielleicht will jetzt schon einer mitarbeiten),
interessiert mich natürlich, was ihr von diesem Ansatz haltet. Ist das
praktikabel? Verbesserungswünsche? Problemstellungen, die nicht
abgedeckt werden?
Lust, mitzumachen?

P.S.: Das ganze wird auch zusätzlich als PHP-Extension gebaut, um bei
Bedarf zusätzliche Performance zu finden...
mepeisen ist offline   Mit Zitat antworten
Sponsor Mitteilung
PHP Code Flüsterer

Registriert seit: 21.08.2005
Beiträge: 4682
PHP-Kenntnisse:
Fortgeschritten

Alt 24.04.2006, 13:34  
Erfahrener Benutzer
 
Registriert seit: 18.07.2004
Beiträge: 2.162
PHP-Kenntnisse:
Fortgeschritten
Basti
Standard

Hallo "mepeisen".

Ich finde in dem Zusammenhang eine wichtige Frage, was für Ausprägungen diese PlugIns annehmen können. Ein PlugIn UserManagement zum Beispiel, beinhaltet dieses dann auch die Views, BusinessObjects (User, Group etc) - bzw. Klassen, die die entsprechenden Interfaces bedienen? Oder werden diese Komponenten nochmal in eigene PlugIns ausgelagert und das PlugIn fordert dann nur eine beliebige Implementierung eines BusinessObject-Interfaces an?

Für meine Zwecke halte ich das Konzept für übertrieben. Ich schreibe gerade an einer Anwendung (einfaches CMS mit ein paar Seitentypen und Benutzer-/Rollen-Verwaltung) und das geht in Richtung Framework, wobei der Anspruch hinter den konkreten Anforderungen steht.

Dort sind die einzigen dynamischen Komponenten Module, die Controller, Action-Klassen/Methoden, Model-Klassen und View-Klassen, sowie (php-)Templates beinhalten und über ein einheitliches Interface über Commands angesteuert werden. Die DbAccess-Objekte für die DaoFactory sollen auch irgendwann mal quasi als Treiber eingesteckt werden können - ist aber gerade keine Anforderung, da alles MySQLi.

Wohin mit den BusnessObjekten ist bei mir auch noch eine offene Frage (kommen wohl in die Core-Bibliothek, da im Moment nicht mehr nötig ist und man später über eine Art Paketverwaltung (bin apt-get-verwöhnt *g) diese BOs als Abhängigkeiten definieren könnte). Die Konfiguration wird dynamisch bei Bedarf geladen.

Was ich sagen will:
Ich hab da verschiedene Komponenten, die später mal über eine Paketverwaltung geladen und je nach Anwendung bzw. Bedarf pro Laufzeit eingebunden werden können. Diese lassen sich aber in Kategorien einteilen (Module, DataAccess-Teiber, BusinessObjects, vielleicht noch Services oder andere Objekte (Mail etc.)) und von daher möchte ich die nicht über einen Kamm scheren, sondern das Laden dieser Komponenten jeweils individuell handhaben (später mal durch einen zentralen PaketManager koordiniert).

Soweit meine Gedanken dazu - geht halt alles ein wenig in eine andere Richtung.

Zur PHP-Extension:
Lässt sich aus einem PHP-Framework eine Extension bauen?

Basti
Basti ist offline   Mit Zitat antworten
Alt 24.04.2006, 17:39  
Erfahrener Benutzer
 
Registriert seit: 21.12.2004
Beiträge: 5.234
PHP-Kenntnisse:
Fortgeschritten
mepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblick
mepeisen eine Nachricht über ICQ schicken mepeisen eine Nachricht über Skype™ schicken
Standard

Zitat:
Ich finde in dem Zusammenhang eine wichtige Frage, was für Ausprägungen diese PlugIns annehmen können. Ein PlugIn UserManagement zum Beispiel, beinhaltet dieses dann auch die Views, BusinessObjects (User, Group etc) - bzw. Klassen, die die entsprechenden Interfaces bedienen? Oder werden diese Komponenten nochmal in eigene PlugIns ausgelagert und das PlugIn fordert dann nur eine beliebige Implementierung eines BusinessObject-Interfaces an?
Nachdem ich darüber überlegt hatte, bin ich irgendwann zu dem Schluss gekommen, dass sich die Plugin-Verwaltung genau um den Punkt nicht kümmern soll. Hintergedanke war auch ein kleines bischen der Aufbau des Eclipse, wo das ganze auch von 0 auf funktioniert.
Beispiel einer potentiellen CMS-Realisierung: Das CMS ist ein eigenes Plugin, was beispielsweise vom zentralen Script per Methode aufgerufen wird, um eine Webseite anzuzeigen. Die Webseiten sind entsprechend hinterlegt (Templates, wie auch immer) und fordern die BusinessObjects und -Logik von anderen Plugins (beispielsweise einem User-Management) an. Und das über genau jene Schnittstelle "GetInterface()". Wenn sich die Plugin-Verwaltung bereits darauf spezialisieren würde und wie ein "CMS" funktionieren würde, wäre es eigentlich schon wieder zu aufgebläht, um als Framework-Basis zu dienen. Gut, ist eine Philosophiefrage aber genau darum geht es mir ja, zu erkennen, ob meine Philosophie gut ist oder nicht

Zitat:
Soweit meine Gedanken dazu - geht halt alles ein wenig in eine andere Richtung.
Nur teilweise. Eine Art Paketierung ist ein interessanter Aspekt. Bisher sind die Gedanken einzig darin, dass man beliebige Plugin-Klassen hat und irgendeine Klasse in der Lage ist, diese anhand von Abhängigkeiten selbst zu laden. Identifiziert werden die Plugins halt über einen Namen bzw. String. VOn daher sind wir da denke ich am Anfang nicht so weit auseinander. Die Funktionalität kann man untereinander nur über allgemeine Interfaces definieren, soweit sind wir auch beisammen. Ich habe hier noch zusätzlich den Zwischenschritt über "GetInterface", um besonders bei größeren Plugins, die immer nur in Teilen gebraucht werden, etwas gezielter zu steuern, wann ich welche Klassen laden müsste.

Erst danach gehts auseinander, da eine Paketierung vom Ansatz her bei mir nicht eingeplant ist. Eine Paketierung zur Strukturierung der Plugins ist sicherlich sinnvoll. Um zu steuern, welche Plugins wann geladen werden, eher überflüssig, da ich wie dargestellt, auch Plugins selber On-Demand lade (beim ersten Verwenden). Das, was du im Paketmanager realisierst, bietet meine Plugin-Verwaltung und das Plugin ist selbstverantwortlich, ob beispielsweise alle Klassen geladen werden oder erst bei Anfrage nach speziellen Interfaces etwas geladen wird.

Die Frage nach dem Huhn oder dem Ei, beispielsweise bei Sessions, habe ich bislang noch nicht zufriedenstellend gelöst, das gebe ich zu. Das merkte ich, als ich vorab auf einem Schmierzettel und im Kopf ein Konzept für ein Cms entwickeln wollte.

Die Treiber-Frage ist sicherlich eine besondere, weil du da immer auf eine BlackBox schaust. Sinnvollerweise kennst du aber etwas, was die BlackBox bedienen kann. Entweder du kennst Interfaces, die du selbst bedienen kannst und gleichzeitig ein Objekt, was dir den Einstieg bietet oder das Framework kennt den Einstieg. Rein vom Ansatz her finde ich es besser (und flexibler), wenn das Framework grundsätzlich nichts von irgendwelchen Datenbanken o.ä. weiss. Daher eher der Ansatz, dieses Hilfskonstrukt "DBManager" nicht in die Plugin-Verwaltung zu integrieren, sondern als Plugin. Spricht aber nichts dagegen, beispielsweise zusätzlich Kategorien aufzubauen, also den anderen Weg zu ermöglichen:

Ein Plugin registriert sich für die Kategorie "DBTreiber" und das Plugin "DBManager" kriegt mit: Neues Plugin und kann dann reagieren. Als aussenstehende Anwendung rufst du DBManager::GetConnection() auf. Bei deinem Ansatz rufst du eine Methode im Paketmanager auf, um eine Datenbank-Verbindung aufzubauen und den zugehörigen Treiber zu laden, soweit ich das sehe.

Zitat:
Zur PHP-Extension:
Lässt sich aus einem PHP-Framework eine Extension bauen?
Aus allen lässt sich eine Extension bauen.
__________________
www.php-maven.org PHP und Maven vereint: Build/Deploy/Produktion/Konfiguration, Projekt Management, CI, PHPUnit, zahlreiche Frameworks
Twitter @ https://twitter.com/#!/mepeisen und Facebook @ http://t.co/DZnKSUih
mepeisen ist offline   Mit Zitat antworten
Alt 24.04.2006, 18:38  
Neuer Benutzer
 
Registriert seit: 22.11.2005
Beiträge: 18
Fat Tony
Standard

Hey!

@mepeisen

Arbeite doch am WebShop Projekt von developers-guide.net mit. Der Shop basiert auf dem MVC System. Der Shop selbst ist ein leistungsfähiges Content Management System mit der Spezialisierung E-Commerce. Er wird auf PHP 5.1 und Mysql 5 / PGSQL 8 und einem Apache Webserver laufen.

Ich habe die Core Klassen sowie die Interfaces und Abstrakten Klassen bereits implementiert. Sofern du Interesse hast, melde dich einfach bei Ben, der gibt dir dann Zugriff auf das WebShop Forum. Gute Programmierer kann man gar nicht genug haben :>.

EDIT:
Es ist natürlich auch jeder andere aufgefordert sich bei developers-guide.net anzumelden und mitzumachen.

MfG Fat Tony
Fat Tony ist offline   Mit Zitat antworten
Alt 24.04.2006, 19:36  
Erfahrener Benutzer
 
Registriert seit: 21.12.2004
Beiträge: 5.234
PHP-Kenntnisse:
Fortgeschritten
mepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblick
mepeisen eine Nachricht über ICQ schicken mepeisen eine Nachricht über Skype™ schicken
Standard

Ma gucken. Wenns das leisten kann was ich suche *g*
__________________
www.php-maven.org PHP und Maven vereint: Build/Deploy/Produktion/Konfiguration, Projekt Management, CI, PHPUnit, zahlreiche Frameworks
Twitter @ https://twitter.com/#!/mepeisen und Facebook @ http://t.co/DZnKSUih
mepeisen ist offline   Mit Zitat antworten
Alt 24.04.2006, 22:25  
Erfahrener Benutzer
 
Registriert seit: 18.07.2004
Beiträge: 2.162
PHP-Kenntnisse:
Fortgeschritten
Basti
Standard

Zitat:
Zitat von mepeisen
Wenn sich die Plugin-Verwaltung bereits darauf spezialisieren würde und wie ein "CMS" funktionieren würde, wäre es eigentlich schon wieder zu aufgebläht, um als Framework-Basis zu dienen. Gut, ist eine Philosophiefrage aber genau darum geht es mir ja, zu erkennen, ob meine Philosophie gut ist oder nicht :-)
Mir gehts nicht darum, (m)ein System danach auszurichten, wie ein CMS funktioniert, sondern danach, wie so ziemlich jede Web-Anwendung funktioniert. Das ist für ich Sinn und Zweck eines Frameworks, dass man quasi ausklammert, was eh immer gleich ist: Anfrage analysieren, entscheiden, wer sie bearbeiten soll, dort dann Rechte prüfen, ggf. Model manipulieren, entscheiden, welche Sicht ausgegeben werden soll, Sicht ausgeben, dabei Daten aus Model ziehen (falls nicht vorher schon reingesetzt). Das Schema ist doch immer so ziemlich das gleiche und praktisch alle Frameworks setzen genau da an, dieses ewige Spielchen einmal festzuklopfen, so das dann nurnoch einzelne Komponenten eingeschoben werden müssen.

Zitat:
Erst danach gehts auseinander, da eine Paketierung vom Ansatz her bei mir nicht eingeplant ist. Eine Paketierung zur Strukturierung der Plugins ist sicherlich sinnvoll. Um zu steuern, welche Plugins wann geladen werden, eher überflüssig, da ich wie dargestellt, auch Plugins selber On-Demand lade (beim ersten Verwenden). Das, was du im Paketmanager realisierst, bietet meine Plugin-Verwaltung und das Plugin ist selbstverantwortlich, ob beispielsweise alle Klassen geladen werden oder erst bei Anfrage nach speziellen Interfaces etwas geladen wird.
Da hab ich mich falsch ausgedrückt. Ein Paketmanager ist bei mir im Moment nicht geplant, sonden nur eine Möglichkeit, wohin es gehen könnte. Dann meinte ich mit "laden" nicht das Einbinden der Klassen zur Laufzeit (ich werwende im Moment __autoload(), so dass ohnehin nur geladen wird, was auch gebraucht wird), sondern, dass man sich Module vom Server runterladen und so bequem in sein System integrieren kann (siehe PEAR oder eben apt-get).

Zitat:
Die Frage nach dem Huhn oder dem Ei, beispielsweise bei Sessions, habe ich bislang noch nicht zufriedenstellend gelöst, das gebe ich zu. Das merkte ich, als ich vorab auf einem Schmierzettel und im Kopf ein Konzept für ein Cms entwickeln wollte.
Das sind halt in meinen Augen solche Basics, dass sie fest in ein Framework reingeklopft werden können. Wozu Session als PlugIn? Jede Webanwendung braucht eine Sitzungssteuerung - zumindest jede, für die man sich überhaupt erst hinsetzen und sie entwerfen würde, oder? Und, klar kann man eine Session-Klasse schreiben, die auch in die Datenbank speichert etc., aber sowas bekommst du auch mit Vererbung oder Dekorierer hin, wenn du eine Basisklasse hast, die auf Sicherheit getrimmt ist und ein paar Optionen kennt.

Basti

PS:
Ich stelle es mir übrigens ein wenig schwierig vor eine solche Anwendung mit lauter PlugIns zu modellieren, oder wie sähe das aus? Du hast ja nachher lauter Subsysteme, die miteinander Kommunizieren, anstatt dass du die Zusammenhänger der Klassen/Objekte direkt abbilden kannst, oder lieg ich da falsch.

PPS:
Mir fiel gard ein, in der CoWiki-Dokumentation was von wegen "alles ist ein PlugIn" gelesen zu haben. Ist dort wohl aber auch wiederum in eine andere Richtung:

http://cowiki.org/130.html#A2
Basti ist offline   Mit Zitat antworten
Alt 25.04.2006, 01:08  
Erfahrener Benutzer
 
Registriert seit: 21.12.2004
Beiträge: 5.234
PHP-Kenntnisse:
Fortgeschritten
mepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblick
mepeisen eine Nachricht über ICQ schicken mepeisen eine Nachricht über Skype™ schicken
Standard

Ich muss mir den Link einmal angucken. Das wichtige ist vielleicht der grundsätzliche Gedankenansatz, der vielleicht mit reinspielt. Ich betrachte das ganze ebend nicht in der klassischen Weise oder aus Request-Sicht. Das Cms, was sich für die Präsentation verantwortlich fühlen wird, ist aus Sicht einer echten Anwendung hin modelliert. Das heisst zu deutsch: Es gibt bei mir zumindest im Framework keine Sicht auf Sessions oder Requests. Das ist ein Detail, was das Cms generiert, um die Anwendung im Browser zu präsentieren. Die Plugins kümmern sich darum gar nicht.

Ich wollte im Cms echte Anwendungen modellieren und entsprechende transparente Modelle für Client (Browser) und Server, sowie Anwendungsbezogene Business-Logik. Vielleicht geht es auch in Richtung Ajax, was das Cms daraus generieren wird, mal gucken... Ich hab da schon so einige Ideen und nach dem, was du oben geschrieben hast, ist mir noch eine hinzugekommen
__________________
www.php-maven.org PHP und Maven vereint: Build/Deploy/Produktion/Konfiguration, Projekt Management, CI, PHPUnit, zahlreiche Frameworks
Twitter @ https://twitter.com/#!/mepeisen und Facebook @ http://t.co/DZnKSUih
mepeisen ist offline   Mit Zitat antworten
Alt 25.04.2006, 01:40  
Erfahrener Benutzer
 
Registriert seit: 18.07.2004
Beiträge: 2.162
PHP-Kenntnisse:
Fortgeschritten
Basti
Standard

Ein CMS ist aber doch nur eine mögliche Form der Web-Anwendung. Beziehungsweise CMS ist in meinen Augen sowas wie ein Wrapper, das in der Regel lauter kleine Anwendungen umhüllt (Seiten aus Content verschiendenen Typs erstellen, anzeigen und berabeiten, Konaktformular, News, Bildergalerie, Gästebuch, Forum, ...). Das besondere am CMS ist ja der Blick durch die Seiten-Brille, d.h. es gibt eine Sitemap in die verscedene Seiten eingehängt werden, die dann Inhalte verschiedenen Typs, eigene Anwendungen (eben vom Kontaktformular bis zum Forum) oder Kompositionen aus beidem enthalten (Startseite mit News).

Aber dir Ablauf: Request -> Dispatcher -> Controller -> Model -> View -> Response (so in etwa) ist ja bei allen Web-Anwendungen mehr oder weniger gleich. Egal, ob es sich dabei um ein banales Gästebuch, ein CMS, eine Ajax-Anwendung, einen rss-Feed oder ein Excel-Export eines Groupware-Terminkalenders handelt.

In sofern betrachtet wäre für mich ein solches Framework vielleicht unattraktiv, wenn ich den ganzen Krempel dann doch wieder in jedem PlugIn implementieren müsste - oder man lagert die übichen Komponenten wie Front-, Page- oder ApplicationController, Request, Respond, TemplateEngine, DaoFactory oder ORM-Komponenten, Session, Logger, Error- und Config-Handling, Objekte für Daten-Strukturen bzw. Kontainer für das Rumschieben von Daten, abstakte Klassen z.B. für die MVC-Elemente, Pages oder was weiß ich wie mans angeht in jeweils eigene PlugIns aus, aber dann sehe ich den Vorteil nicht, wenn du die eh bei jeder neunen Anwendung einbindest (gegenüber einer festen Implementierung im Framework).

Klar, du könnest dir so alles beliebig zusamenbasteln, aber letztlich gibt es so doch dann auch nur einige wenige Konstellationen, die für eine Anwendung Sinn machen. PluGins, wie die Sitzungssteuerung, Logging, Error-Handling etc. kannst du dann natürlich mitt allen Konstellationen verwenden, aber das sind doch eh nur je 1, 2, 3 Klassen, die du sonst ja auch einfach gegen eine zentrale Bibliothek verlinken kannst.

Was wäre der Vorteil. Beziehungsweise anders gefragt: Inwelchen Situationen und Anwendungfällen würden Vorteile entstehen und welche?

Ich hab immer noch nicht verstanden, was genau diese PlugIns dann sein sollen, aber warum sollte ich a) PlugIn laden, b) Interface holen, c) drauf zugreifen, wenn ich einfach $Session = Session->getInstance()->set('foo', 'bar'); oder Logger::warning('bla'); oder $this->Config = new Config; $this->Config->get('ApplicationController', 'defaults'); oder ähnliche Zugriffe benutzen kann?

Ich will nichts schlecht reden, nur verstehen wozu, mit welchem Gewinn, zu welchem Preis.

Basti
Basti ist offline   Mit Zitat antworten
Alt 25.04.2006, 17:13  
Erfahrener Benutzer
 
Registriert seit: 21.12.2004
Beiträge: 5.234
PHP-Kenntnisse:
Fortgeschritten
mepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblickmepeisen ist ein wunderbarer Anblick
mepeisen eine Nachricht über ICQ schicken mepeisen eine Nachricht über Skype™ schicken
Standard

Hmmm. In einem Punkt bin ich schonmal richtig ins Grübeln gekommen: Ich habs wohl irgendwie falsch erklärt. Nochmal ganz von vorne.

Betrachten wir das nicht PHP-spezifisch. Die Intention ist eine spezielle Betrachtungsweise einer "Webseite". Die typisch Webseite unterliegt einem starrem oder klassischem Konzept. Ausgehend von J2EE oder meinetwegen auch Ajax lässt sich dieses Konzept aufweichen und wir gelangen zu einem völlig neuen Blickwinkel, wenn wir es konsequent machen:


Das Anwendungsmodell

Wenn man einmal alles als ganzes betrachtet, haben wir einen Client-Teil und einen Server-Teil einer verteilten Anwendung. Wodurch zeichnet es sich aus? Das Anwendungsmodell wird nicht nur als ganzes betrachtet, sondern auch als solches entwickelt und betrieben. Wir lösen uns vom klassischen Gedanken an eine Webseite, die durch Klicken auf einen Button oder Link eine neue Webseite lädt.

Die Webseite dient lediglich der Präsentation der Anwendung, die im Browser und im Webserver läuft. Die Komponenten, die diese Präsentation bestimmen, sind als Komponenten zu sehen und nicht als Webseiten. Das, was der User dann zu Gesicht bekommt, ist eine "echte" Anwendung, auch vom Funktionsumfang her. Popup-Dialoge, Panels, Layouts usw. Ob das ganze dann im Ergebnis als Webseite mit Links zum Klicken realisiert wird oder als das, was man im allgemeinen heute unter dem Ajax-Konzept versteht, interessiert die ApplicationLogik nicht. Für die Anwendung aus Server-Sicht ist wichtig, wann der Benutzer mit ihr interagiert und was der Benutzer will, nicht wirklich, auf welche Weise er das macht. Es soll eine Anwendung A (meinetwegen ein Live-Newssystem) auch überhaupt nicht interessieren, was der Benutzer gerade in Anwendung B so treibt. Die Anwendung A soll laut Definition in der Lage sein, auch im Browser im Hintergrund weiterzulaufen und bei Bedarf eine Live-Nachricht hochzupoppen.

Hierbei liegt der Schwerpunkt natürlich nicht auf der Webseite, sondern auf der Anwendung. Würde man dererlei Konzepte in einem klassischen, seitenbasierendem Template-System realisieren, stößt du sehr schnell an deine natürliche Grenzen, ganz einfach weil die Templates derart komplex werden, dass du sie nicht mehr warten kannst. Für den Bereich des Backends magst du das noch struktrieren können in gezielt aufgebaute und eingesetzte Businessobjekte und -Logik. Aber wie sieht sowas als "Webseite" aus?

Aus diesem Grund beschäftigt sich der Grundgedanke des Frameworks genau mit diesem Punkt in einem großen Zusammenhang. Willst du dir multiple Systeme und Subsysteme oder Module oder Komponenten (ich nenns Plugins), die vielleicht nur sehr triviale Sachen gemeinsam haben (meinetwegen Benutzerlogin), in einer ganzeinheitlichen Anwendung integrieren, stößt du immer wieder auf die gleichen Probleme. Immer wieder musst du dafür sorgen, dass deine Templates Dinge berücksichtigen, die vielleicht gar nichts darin zu suchen habe. Nehmen wir einmal genau das Beispiel mit einer hochpoppenden Nachricht eines im Hintergrund ablaufenden Nachrichtensystems. Wie schnell hat man das in die Templates einer bestehenden Webseite integriert, dass ein eingeloggter User live Nachrichten empfangen kann, die hochgepoppt werden (und zwar nicht als JavaScript alert?) Was das bedeuten könnte, wäre klar. Also macht man es nicht, sondern sucht sich andere Lösungen (javascript->alert) oder einen eigens dafür vorbereiteten Bereich im Design, wo dann ein kleiner Message-Bereich auftaucht usw.
Das bedeutet aber im Ergebnis: Du richtest deine Bedürfnisse nach deiner Anwendung aus und nicht deine Anwendung nach deinen Bedürfnissen. Das mag jetzt kleinkariert klingen aber Beispiele, wo man aufgrund der starren Struktur einer klassischen Webseite Kompromisse schliesst, kennen wir alle mehr als genügende. "Weils halt im Internet so ist." Und?

Ausgehend von diesem Problem habe ich mir gesagt, dass es doch möglich sein muss, genau solche Denkansätze mal wegzulassen. Es muss doch möglich sein, genau deswegen mal wegzukommen von dieser Betrachtungsweise. Was wäre, wenn wir obiges Beispiel nehmen könnten und eine unsichtbare Komponente definieren (völlig losgelöst ob da was in der Webseite auftaucht oder nicht), die, sobald eine Nachricht für den User eintrifft, ein HTML-Element mit dieser Nachricht einblendet, so dass der User sie sieht. Dabei soll es völlig schnurzpiep egal sein, was mit dem aktuellen Design ist, denn wir wollen ja eine Art "Popup-Nachricht". Es soll völlig egal sein, was der User aktuell gerade getrieben hatte. Klickt er die Nachricht weg, kann er mit der angefangenen Arbeit fortfahren.



Und jetzt in PHP

Dieses Beispiel nehmen wir jetzt und realisieren es als PHP-Framework, wobei wir uns noch viele Gedanken über Verallgemeinerungen machen. Klingt stark nach Ajax, wobei ich persönlich das Wort Ajax erst in diesem Zusammenhang in den Mund nehmen werde, wenn es um die tatsächliche Präsentation geht. Denn Ajax ist nur ein kleiner (wenn auch ev. wichtiger) Bestandteil eines solchen Konzepts. Es beschreibt eine mögliche Lösung für die Ablaufsteuerung im Browser und die Kommunikation der verteilten Anwendung, nicht aber den Rundumschlag, den ich erreichen will.

Das Grundkonzept sollte nun denke ich (oder hoffe ich) irgendwie klar geworden sein. Genau dieses Grundknzept nehme ich nun und zerlege ich in Teilprobleme. Eines dieser Teilprobleme ist die Steuerung der Modularisierung, womit wir wieder beim Anfang wären und meinen Gedanken zu "Plugins". Das ganze natürlich ziemlich wiederverwertbar, was bedeutet, dass man diese relativ einfache Plugin-Steuerung auch gut für andere Zwecke verwenden kann, da sie vom Rest des Frameworks nicht abhängig sein wird. Gut, was ich mitgenommen habe ist schonmal folgendes:
- Nach genauerem Überlegen ist der Name "Plgugin" eventuell etwas unglücklich gewählt für das, was es beschreibt. Modul trifft es ev. etwas besser und klarer.
- Eine Gruppierung jeglicher Art wäre zur Steigerung der Übersicht und Handhabbarkeit vielleicht gar nicht schlecht. Sei die Gruppierung nun technisch oder fachlich motiviert.
- Spezielle Plugins oder Module sollten noch einmal gesondert betrachtet werden. Der Einwand mit den Treibern trifft es oben ziemlich gut. Das ist sicherlich nicht glücklich gelöst, wenn man es dabei belässt. Vielleicht sollte das Plugin-System durchaus noch einige Spezialisierungen zu besonderen Arten von Plugins oder Modulen bieten können.

Auch wenn es nicht Intention des ganzen ist, wird das Framework auch in der Lage sein, als (eventuell aufgeblähtes) CMS-System für klassische Webseiten (seien sie dynamisch oder nicht) dienen zu können. Das hat auch einen einfachen Hintergrund: Es gibt auch für solche Verteilte Anwendungen immer auch genügend "Nicht-dynamische" Bereiche. Also die klassischen Informationsseiten, Impressum etc. Der Einfachheit halber sollte man hier nie den Weg verbauen, auch HTML-Bereiche einbinden zu können. Das ist aber nur als Randnotiz zu verstehen und nicht erklärtes Ziel.



Modellbetrachtung

Hier habe ich noch nicht für eine Vorgehensweise entschieden. Zunächst einmal gibt es zwei mögliche Varianten, die es zu diskutieren gäbe.
a) Getrennte Modelle für Client und Server.
Server und Client greifen auf unterschiedliche Modelle zu, die selbstverantwortlich synchronisert werden müssen. Das heisst, dass eine Benutzerinteraktion (Bestätigen eines Formulars als klassisches Beispiel), die eine Modelländerung veranlasst, dann beispielsweise per XML zum Server kommuniziert wrden muss, der entsprechend die Änderungen vornimmt. Das wäre jetzt das Bild von AJAX. Nachteil des ganzen ist, dass teilweise unnötiger Overhead erzeugt wird während der Entwicklung, da man sich auch um Belange der Synchronisation (und damit der Kommunikation) kümmern müsste.

b) Geteilte Modelle.
Deutlich eleganter für viele Aufgaben finde ich eine Lösung, die ein geteiltes Modell zulässt. Das heisst, dass man das Modell Live verändern kann und entsprechend auf Server und Client synchron gehalten wird.

c) Der Kompromiss
Die wohl beste Art, Daten zu verwalten, ist die Kombination aus beiden Varianten. Das Datenmodell kann berücksichtigen, dass es synchron gehalten werden kann aber nicht muss. Daten, die so nur auf dem Client verfügbar sind und nur dort gebraucht wird, sollen auch dort verbleiben. Gleiches gilt für den Server.
Wenn man aber Daten hat, die sowohl auf Client als auch auf Server existieren, sollte man sie ohne weiteres Zutun bei Bedarf synchronisieren können. Man stelle sich beispielhaft eine JavaScript-Methode vor, die "synchronizeToServer" heisst bzw. "synchronizeFromServer" und eine entsprechende PHP-Methode.


Beispiel zum Modell:
Man stelle sich eine Art Wizard vor. Ich denke jeder weiss, was ein Wizard darstellt. Dieser Wizard zeichnet sich dadurch aus, dass er ein Modell komplett bearbeitet aber in mehreren Schritten. Jederzeit ist ein "Vor" und ein "Zurück", sowie ein "Abbrechen" und ggf. ein "Speichern" möglich. Betrachtet aus einer Webseite wird dies eine Kaskade aus Webseiten, Session-Verwendung u.ä. Betrachtet man es allerdings als Anwendung, wird die Komponente "Wizard" mit den einzelnen Wizard-Seiten definiert. Am Client wird ein Modell definiert und immer weiter bearbeitet. Es mit Abspeichern des Wizards wird das Modell zum Server übertragen und im Server-Teil der Anwendung verarbeitet.


so far....
Vielleichts wird ein bischen verstänlicher mit diesem etwas längeren Post...
__________________
www.php-maven.org PHP und Maven vereint: Build/Deploy/Produktion/Konfiguration, Projekt Management, CI, PHPUnit, zahlreiche Frameworks
Twitter @ https://twitter.com/#!/mepeisen und Facebook @ http://t.co/DZnKSUih
mepeisen ist offline   Mit Zitat antworten
Alt 25.04.2006, 23:00  
Erfahrener Benutzer
 
Registriert seit: 18.07.2004
Beiträge: 2.162
PHP-Kenntnisse:
Fortgeschritten
Basti
Standard

Hallo "mepeisen".

Zitat:
Zitat von mepeisen
Wir lösen uns vom klassischen Gedanken an eine Webseite, die durch Klicken auf einen Button oder Link eine neue Webseite lädt.
Hast du dir mal PRADO angeschaut? Dieses Framework ist zwar auch auf klassische "eine Seite per Request" ausgelegt (gibt sicher auch Bestrebungen, XMLHttpRequest/Ajax zu integrieren, weiß aber nicht ob und wie weit), emuliert aber quasi eine normale GUI-Anwendung, indem der Viewstate stets gespeichert wird und durch Klicks auf Links (das System erzeugt JavaScript-Links) bzw. Abschicken von Formularen werden Events ausgelöst, die durch die jeweiligen Komponenten interpretiert werden und dann ihre eigene View ändern oder auch andere Komponenten im ViewTree austauschen, wobei der Rest eben wieder so angezeigt wird, we gehabt.

Du plazierst also keine Links etc. in deine Komponenten-Templates, sondern eben Events, die durch Klicken an die EventHandler der Komponente zurückgegeben werden. Damit hast du in jedem Fall schonmal dieses Request - Respond - Request - Respond aus der Logik der für die jeweilige Anwendung zu implementierenden Komponenten verbannt.

Schwierig an dem Konzept finde ich die Kommunikation unter/zwischen den Komponenten. Wenn du also eine Komponente hast, die eine LogIn-Box am rechten UI-Rand hast und dort die Benutzerdaten abschickst, dann wird diese womöglich zu einem Benutzer-Menü mit LogOut-Button, Prefs-Link etc. Aber glechzeitig soll vielleicht im Hauptfenster eine andere Sicht geladen werden (z.B. die anstehenden Aufgaben des Benutzers). Das wird in PRADO, soweit ich mich erinnere, so gelöst, indem die Komponente (LogIn/User-Box) direkt auf die Hauptfenster-Komponente zugreift. Das bedeutet aber, dass diese Komponente von der anderen wissen muss und somit schon wieder an einen ganz speziellen Anwendungkontext gebunden ist, also nicht ohne Anpassung wiederverwertbar ist.

Hier müsste man vielleicht dann anstatt dessen von den Komponenten Signale an eine zentrale Stelle (ApplicationController, whatever) übergeben, die dann anhand der Anwendungskonfiguration entscheidet, wie die anderen Komponenten reagieren sollen.

Oder es werden Abhängigkeiten definiert und mittels Observer oder so umgesetzt, so das Änderungen des Models/derView einer Komponente wiederum Events bei den abhängigen Komponenten auslösen. Aber auch da muss die abhängige Komponente auf die entsprechenden Signale vorbereitet sein.

Oder es werden bestimmte Geschäftsobjekte eingesetzt, die von den Komponenten überwacht werden. Beispiel wieder einen klassischen Website: Ein Link im Hauptmenü löst ein Event im Modul Menü aus. Dieses ändert die CurrentNode des Sitemap-Objektes. Dieses wiederum wird vom "Hauptfenster" überwacht, welches sich nach der entsprechenden Miteilung der Änderung durch das Sitemap-Objekt selbst anhand des neuen Sitemap-Status aktualisiert.

Problem an der ganzen Geschichte ist halt, dass du den ganzen Krempel in den Sessions speichern musst. Bei normalen Web-Anwendungen vielleicht sogar für jede Instanz der Anwendung im Browser (so dass man unabhängig in mehreren Tabs/Browser-Fenstern auf der Site surfen kann). Da hat Ajax natürlich einge Vorteile - ist für mich aber noch nicht wirklich interessant.

Jo, ich mach hier mal Stop. Vielleicht weichen meine Gedanken ja weit von deinen ab...

Basti
Basti ist offline   Mit Zitat antworten
Antwort


Themen-Optionen
Thema bewerten
Thema bewerten:

Forumregeln
Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are an
Gehe zu

Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
Design und Code Trennen TeazY PHP Tipps 2008 29 21.05.2008 12:08
Anschaffung eines Editors, diverse Fragen notyyy Off-Topic Diskussionen 12 30.11.2007 16:02
Design Beitragsarchiv 26 04.06.2005 20:56
Problem mit *.TPL dateien und Design imported_DJ Nuno HTML, Usability und Barrierefreiheit 4 08.03.2005 02:29
[PHP] Design Schutz für ein Gästebuch I-Spy PHP Tipps 2005 5 01.01.2005 11:25
[Erledigt] Fragen über Fragen... wer kann helfen? PHP Tipps 2004 2 08.07.2004 21:12
Design an PHPnuke oder TriggerTG anpassen?? PHP Tipps 2004 5 11.06.2004 15:29

Besucher kamen über folgende Suchanfragen bei Google auf diese Seite
php5 konzept plugin, framework plugin steuerungstool, php framework rollenverwaltung, rollenverwaltung server 2008 framework, telefonnummer martin eisengardt,pfinztal, name eisengardt, applicationcontroller::request

Alle Zeitangaben in WEZ +2. Es ist jetzt 21:15 Uhr.




Powered by vBulletin® Version 3.7.2 (Deutsch)
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
Aprilia-Forum, Aquaristik-Forum, Liebeskummer-Forum, Zierfisch-Forum, Geizkragen-Forum

Creative Commons License
Dieser Inhalt ist unter einer Creative Commons-Lizenz lizenziert.