18.2 Praxis im Blog 

Der letzte Abschnitt 18.1, »Architektur«, hat Ihnen die beiden Komponenten, mit denen nun im GP-Blog gearbeitet wird, größtenteils theoretisch vorgestellt.
Das erste Beispiel wird Ihnen helfen, ein existierendes Schema aus einer Datenbank auszulesen und in einer Datei zu speichern. Diese Datei wird im zweiten Beispiel benötigt, im eigentlichen Setup-Programm des GP-Blogs, das unter anderem die Einrichtung der benötigten Datenbankstruktur übernimmt. Hier werden Sie auch die Configuration-Komponente wiedersehen, die im Setup-Programm zum Schreiben der Konfiguration benutzt wird.
18.2.1 Das aktuelle Schema auslesen 

Das Ziel des im nächsten Abschnitt besprochenen Setup-Programms ist, die Einrichtung des GP-Blogs für einen Benutzer zu vereinfachen. Hauptproblem ist hierbei die Einrichtung der Datenbankstruktur, mit der insbesondere technisch nicht versierte Benutzer überfordert sein könnten.
Zur Hilfe kommt Ihnen in diesem Fall die DatabaseSchema-Komponente, die es Ihnen ermöglicht, Ihrer Anwendung die Struktur der benötigten Datenbank in abstrakter Form mitzuteilen und mit Hilfe der Komponente installieren zu lassen. Doch woher nehmen Sie die abstrakte Darstellung Ihres Datenbankschemas? Diese Frage soll ein kleines Konsolenprogramm beantworten, das die Datenbankstruktur des GP-Blogs für Sie ausliest und in einer XML-Datei abspeichert.
Vorbereitungen
Zunächst wird im Konsolenprogramm eine neue Applikationsbasis benötigt. Diese lagern wir vorausschauend in eine Datei config.php aus, die Sie in einem neuen Verzeichnis namens setup/ in der zum Kapitel gehörenden stage16/ finden.
require_once dirname( __FILE__ ) .
"/../../ezc/trunk/Base/src/base.php";
ezcBase::addClassRepository(
dirname( __FILE__ ) . "/..",
dirname( __FILE__ ) . "/../autoload",
"gp"
);
function __autoload( $className )
{
ezcBase::autoload( $className );
}
$out = new ezcConsoleOutput();
$out->formats->success->color = 'green';
$out->formats->success->style = array( 'bold' );
$out->formats->error->color = 'red';
$out->formats->error->style = array( 'bold' );
$out->formats->error->target = ezcConsoleOutput::TARGET_STDERR;
$out->formats->info->color = 'blue';Listing 18.8 Die Datei config.php initialisiert die Konsolenumgebung.
Zunächst wird der bereits aus Kapitel 2, »Einführung in eZ Components«, bekannte Autoload-Mechanismus initialisiert, der ebenfalls die Klassen des GP-Blogs laden kann. Ist dies passiert, wird in der Variablen $out die in beiden Programmen häufig benötigte Instanz der Ausgabeklasse gespeichert. Außerdem werden einige Formate konfiguriert.
Das Format success wird verwendet, um Erfolgsmeldungen auszugeben. Diese erscheinen in leuchtendem Grün. Mit error formatierte Texte strahlen in leuchtendem Rot, denn sie stellen Fehlerinformationen dar. Fehler werden außerdem nicht wie Erfolgsmeldungen in die normale Standardausgabe geschrieben, sondern in die Standardfehlerausgabe. Das Format info wird für allgemeine Informationen verwendet, auf die der Benutzer gesondert aufmerksam gemacht werden muss.
Eingaben verarbeiten
Als Nächstes werden die zwei benötigten Eingaben des Benutzers verarbeitet. Dies ist zum einen der DSN einer Datenbank, deren Struktur auszulesen ist, sowie ein Dateiname für die Datei, in die das gelesene Schema abgelegt wird. Sie sehen, dass Sie mit dem Programm eine beliebige Datenbankstruktur auslesen und in einer der Komponente DatabaseSchema passenden Form speichern können, solange die Datenbankstruktur nicht den Einschränkungen der Komponente widerspricht.
require_once dirname( __FILE__ ) . '/config.php';
$in = new ezcConsoleInput();
$in->argumentDefinition = new ezcConsoleArguments();
$in->argumentDefinition[0] = new ezcConsoleArgument( 'dsn' );
$in->argumentDefinition[0]->shorthelp =
'DSN of the database to dump.';
$in->argumentDefinition[1] = new ezcConsoleArgument( 'dest' );
$in->argumentDefinition[1]->shorthelp =
'Destination file for the dump.';
try
{
$in->process();
}
catch ( ezcConsoleException $e )
{
$out->outputLine(
'An error ocurred: ' . $e->getMessage(),
'error'
);
$out->outputLine();
$out->outputLine(
$in->getHelpText( 'GP-Blog database dumper' )
);
die();
}
$dest = $in->argumentDefinition['dest']->value;
$dsn = $in->argumentDefinition['dsn']->value;
if ( file_exists( $dest ) )
{
$out->outputLine( "File $dest already exists.", 'error' );
die();
}Listing 18.9 Argumente an das Programm werden verarbeitet.
Zunächst wird die soeben vorgestellte Datei config.php inkludiert und damit das in $out gespeicherte Ausgabeobjekt global verfügbar. Die erste Aktion besteht darin, Argumente zu verarbeiten, wofür die Klasse ezcConsoleInput zuständig ist. In der Instanz dieser Klasse werden zwei Argumente dsn und dest (in dieser Reihenfolge) definiert und mit einem informativen Hilfetext versehen.
Zwar konfigurieren wir keine Hilfeoption – wie Sie dabei vorgehen, wird im Setup-Programm erläutert –, doch wird die Hilfe angezeigt, falls die verlangten Argumente nicht übergeben wurden. Zuvor zeigt der entsprechende catch-Block, der die Verarbeitungsfehler der Argumente auffängt, dem Benutzer den Fehler an. Beispielhaft sehen Sie dies im Screenshot zusammen mit der Ausgabe der Hilfe:
Abbildung 18.5 Automatisch generierte Hilfe des Schema-Lese-Programms
Diese Ausgabe entsteht, wenn das Programm völlig ohne Parameter aufgerufen wird. Zunächst zeigt die Fehlermeldung (in leuchtendem Rot) an, dass das Argument dsn nicht übergeben wurde, aber erforderlich ist. Der automatisch generierte Hilfetext (hier in Weiß) beginnt stets mit dem Wort Usage:, gefolgt vom Namen des ausgeführten Programms und der Synopsis des Aufrufs.
Die Synopsis, generiert von der getSynopsis()-Methode des Eingabeobjekts, enthält zunächst keine Optionen, da keine konfiguriert wurden. Der optionale (gekennzeichnet durch die eckigen Klammern), gebräuchliche Trenner -- von Optionen und Argumenten erzeugt die Synopsis-Methode automatisch, da der Parser diesen verarbeiten kann. Danach sind die beiden konfigurierten Argumente dargestellt, jeweils inklusive des erforderlichen Typs.
Die dem Aufruf von getHelpText() übergebene Programmbeschreibung wird auf der folgenden Zeile ausgegeben, gefolgt von einer Leerzeile. Dieser würden normalerweise die Erläuterungen zu den Optionen folgen. Da hier aber keine definiert wurden, folgt direkt die Beschreibung der Argumente.
Wurden beide Argumente – keines wurde als optional markiert – übergeben, so tritt keine Exception bei der Verarbeitung auf. In den nachfolgenden zwei Zuweisungen können beide Argumentwerte über die Namen der Argumente adressiert werden. Zum Schluss überprüft der hier gezeigte Abschnitt, ob die übergebene Datei bereits existiert, da diese nicht überschrieben werden soll.
Das Schema auslesen
Im nächsten Teil des Skripts wird das Datenbankschema anhand der übergebenen Argumente, die jetzt in $dsn und $dest zur Verfügung stehen, ausgelesen.
try
{
$db = ezcDbFactory::create( $dsn );
}
catch ( ezcDbException $e )
{
$out->outputLine( "Could not connect to $dsn", 'error' );
die();
}
$out->outputLine( "Successfully connected to $dsn", 'success' );
try
{
$schema = ezcDbSchema::createFromDb( $db );
}
catch ( ezcDbSchemaException $e )
{
$out->outputLine(
"Could not read schema from $dsn",
'error'
);
die();
}
$out->outputLine(
"Successfully read schema from $dsn",
'success'
);Listing 18.10 Das Schema auslesen
Zunächst wird zum Auslesen des Schemas eine Datenbankverbindung benötigt, wie Sie sie aus Kapitel 6, »Datenbankanbindung«, bereits kennen. Der als DSN übergebene Parameter wird der Factory-Methode der Database-Komponente übergeben. Ein try-catch-Block garantiert, dass die Verbindung hergestellt werden kann. Ist dies nicht der Fall, liefert die Anwendung eine entsprechende Fehlermeldung aus und beendet sich.
Die gleiche Reaktion hat der zweite catch-Block zur Folge. Bevor dieser in Kraft treten kann, ist zumindest die Verbindung zur Datenbank erfolgreich hergestellt worden, was dem Benutzer mit einer Erfolgsmeldung mitgeteilt wird. Der Code im nächsten try-Block versucht, das Schema der Datenbank auszulesen. Gelingt dies nicht, setzt der soeben beschriebene catch-Block ein und beendet die Ausführung. Eine mögliche Ursache können hier fehlende Berechtigungen in der Datenbank sein.
Ein erfolgreiches Auslesen des Schemas wird dem Benutzer durch eine erneute Erfolgsmeldung angezeigt. Das abstrakte Schemaobjekt zur Darstellung der Datenbankstruktur steht also nun in $schema zur Verfügung.
Abstraktes Speichern
Nun soll dieses Schemaobjekt noch als XML-Datei gespeichert werden. Dies geschieht im letzten Programmabschnitt:
try
{
$schema->writeToFile( 'xml', $dest );
}
catch ( ezcDbSchemaException $e )
{
$out->outputLine(
"Could not write schema to $dest",
'error'
);
die();
}
$out->outputLine(
"Successfully wrote schema to $dest",
'success'
);
$out->outputLine( 'Thank you for using this tool. :)' );Listing 18.11 Das gelesene Schema abspeichern
Der letzte try-Block des Programms versucht, das Schemaobjekt dazu zu bewegen, sich mittels eines entsprechenden Backend-Handlers in eine XML-Datei zu speichern. Scheitert dies, wird eine entsprechend formatierte Fehlermeldung ausgegeben und das Programm beendet.
| Testen des Schema-Lese-Programms |
|
Das Programm zum Lesen des Datenbankschemas ist in einem eigenen, neuen Ordner gelandet. Sie finden es in stage16/ unter setup/readdb.php. Rufen Sie dieses Programm aus dem Hauptverzeichnis der Stage mit Hilfe Ihres PHP-Konsolen-Interpreters auf, sollte die zuvor gesehene Fehlermeldung erscheinen. Übergeben Sie dem Programm den DSN, den Sie auch in Ihrer GP-Blog-Konfiguration unter config/site.ini eingestellt haben, und den Namen einer noch nicht existierenden Datei, so speichert das Programm das Schema des GP-Blogs entsprechend als XML-Datei. |
|
Möchten Sie dies nicht ausprobieren, können Sie stattdessen eine vorbereitete Version unter stage16/setup/schema.xml einsehen. Das XML-Format ist nicht sonderlich spannend. Auch wenn Sie planen, eigene Backend-Handler zu entwickeln, arbeiten Sie zumeist nur mit ezcDbSchema-Objekten anstatt mit der XML-Darstellung. |
Geht alles gut, teilt das Programm den Erfolg mit und terminiert erfolgreich mit einer abschließenden Meldung. Haben Sie diesen Status bei Ihren Tests bereits erreicht, können Sie nun einen Blick in die von der DatabaseSchema-Komponente erzeugte XML-Datei mit der Beschreibung der GP-Blog-Datenbank in der von Ihnen angegebenen Datei werfen.
18.2.2 Ein Setup-Programm 

Nachdem Sie gesehen haben, wie das Datenbankschema des GP-Blogs ausgelesen wird und dabei erste praktische Erfahrungen mit den beiden Komponenten DatabaseSchema und ConsoleTools gesammelt haben, gehen wir nun auf die eigentliche Setup-Anwendung ein. Diese wird einige Optionen vom Benutzer erfragen und dazu Dialoge anstatt Optionen oder Argumente verwenden, diese in der entsprechenden Konfigurationsdatei speichern und daraufhin die Datenbank einrichten.
Natürlich handelt es sich hierbei nicht um eine vollwertige Setup-Routine, die alle Einstellungsmöglichkeiten des Blogs komfortabel verwalten kann. Wir geben zum einen der Routine eine Hilfeoption mit, zum anderen kann dem DSN zur Installation der Datenbank per Option vorgeschrieben werden, den entsprechenden Dialog zu überspringen. Es ist sinnvoll, einen ähnlichen Mechanismus für alle Einstellungen bereitzustellen, sodass eine vollautomatisierte Installation möglich wird. Wir verzichten an dieser Stelle aber darauf.
require_once dirname( __FILE__ ) . '/config.php';
$in = new ezcConsoleInput();
$helpOpt = $in->registerOption(
new ezcConsoleOption(
'h',
'help'
)
);
$helpOpt->shorthelp = "Help information.";
$helpOpt->longhelp = "Gives you help information on the
usage of this program.";
$helpOpt->isHelpOption = true;
$dsnOpt = $in->registerOption(
new ezcConsoleOption(
'd',
'data-source-name'
)
);
$dsnOpt->shorthelp = 'Data source name for installation.';
$dsnOpt->longhelp = 'Data source name of the database
to install the GP-Blog schema into. Provide if you
want to skip this step during installation.';
$dsnOpt->type = ezcConsoleInput::TYPE_STRING;
try
{
$in->process();
}
catch ( ezcConsoleException $e )
{
$out->outputLine(
"Could not process options: '{$e->getMessage()}' . ",
'error'
);
die();
}
if ( $in->helpOptionSet() !== false )
{
$out->outputLine(
$in->getHelpText( 'GP-Blog Setup' ),
'help'
);
die();
}
$dsn = $dsnOpt->value;Listing 18.12 Hilfe- und DSN-Option verarbeiten
Zunächst erscheint die Inkludierung der bereits aus dem letzten Abschnitt bekannten Datei config.php, die das Basisausgabeobjekt in $out bereitstellt. Im nächsten Schritt erzeugt dieses Programm eine Instanz von ezcConsoleInput, registriert an diesem aber zwei Optionen, statt wie im letzten Abschnitt zwei Argumente.
Die Hilfeoption wird zuerst registriert. Sie ist für den Benutzer mit Hilfe der Parameter -h und --help erreichbar. Außerdem wird ezcConsoleInput bekanntgegeben, dass es sich bei dieser Option um die Hilfeoption der Anwendung handelt. Sollte sie während der Verarbeitung auftauchen, ignoriert das Eingabeobjekt alle weiteren Regeln.
Die zweite Option, -d oder --data-source-name, kann dem Benutzer zur beschriebenen Automation dienen. Wird sie gesetzt, so erwartet sie als Wert den DSN einer Datenbankverbindung, wie Sie ihn von der Database-Komponente her kennen.
Beide Optionen werden mit Informationen zur Darstellung in der Hilfe mitgegeben. Im Gegensatz zum letzten Beispiel wird die Hilfe allerdings nicht ausgegeben, falls keine Option gesetzt ist. In diesem Fall startet das normale Setup-Programm. Nur wenn explizit die Hilfeoption isHelpOptionSet()gesetzt ist, werden Hilfeinformationen angezeigt. Ansonsten wird der Wert der Option –data-source-name in $dsn gespeichert. Er ist entweder false, falls die Option nicht gesetzt war, oder enthält einen String, der zur Verbindungsaufnahme mit der Datenbank verwendet werden soll.
Konfiguration lesen
Der wichtigste Punkt im Rahmen der Installation ist die Einrichtung der Datenbank, weshalb dieser Punkt zuerst erledigt wird. Zuvor lädt das Programm jedoch die bisherige Konfiguration.
$iniReader = new ezcConfigurationIniReader();
$iniReader->init( dirname( __FILE__ ) . '/../config', 'site' );
$res = $iniReader->validate();
if ( count( $resList = $res->getResultList() ) > 0 )
{
foreach( $resList as $resItem )
{
$out->outputLine(
sprintf(
"Your configuration has an error in file '%s' on
line '%d',
position: '%d': %s\n",
$item->file,
$item->line,
$item->column,
$item->details
),
'error'
);
}
die();
}
$cfg = $iniReader->load();
$out->outputLine( 'Welcome to the GP-Blog setup!' );Listing 18.13 Konfiguration einlesen
Relativ zur Position des Setup-Programms wird die Konfigurationsdatei config/site.ini zunächst auf Fehler hin überprüft. Treten hierbei Fehler auf, werden die beim Parsen der Datei aufgetretenen Fehler ausgegeben und das Programm wird beendet. Verläuft die Validierung korrekt, begrüßt das Programm den Anwender.
Verbindung aufnehmen
Wurde dem Programm bereits ein DSN für die Datenbankverbindung übergeben, so wird kein weiterer Dialog für die Abfrage dieser Einstellung verwendet.
$db = false;
do
{
if ( $dsn === false )
{
$q = new ezcConsoleQuestionDialog( $out );
$q->options->validator = new
ezcConsoleQuestionDialogRegexValidator(
'@\w+://[-\w:\@]+/\w+@',
$cfg->getSetting( 'installation', 'dsn' )
);
$q->options->text = 'What is your DSN (data
source name)?';
$dsn = ezcConsoleDialogViewer::displayDialog( $q );
}
try
{
$db = ezcDbFactory::create( $dsn );
}
catch ( Exception $e )
{
$out->outputLine(
'Could not connect to database. Reason:',
'error'
);
$out->outputLine( $e->getMessage(), 'error' );
$db = false;
$dsn = false;
}
}
while ( $db === false );
$out->outputLine(
'Successfully connected to database.',
'success'
);Listing 18.14 Verbindung zur Datenbank herstellen
Zunächst wird die Variable, die die Datenbankverbindung aufnehmen soll, mit false initialisiert, da noch keine Verbindung existiert. Es wird hier eine do-while-Schleife verwendet, die mindestens einmal durchlaufen wird. Dies ist erforderlich, um eine Verbindung zur Datenbank zu erhalten.
Innerhalb der Schleife wird zunächst getestet, ob der DSN für die Datenbankverbindung bereits bekannt ist. Im ersten Durchlauf ist dies denkbar, falls der Anwender die entsprechende Option gesetzt hat. Der ansonsten angezeigte Fragendialog wird mit Hilfe von ezcConsoleDialogViewer solange angezeigt, bis der Anwender einen DSN eingibt. Das verwendete Regex-Prüfer-Objekt validiert die Form eines DSN, sodass die Verbindungsaufnahme zumindest erfolgversprechend erscheint.
Anschließend wird der Versuch gestartet, den so erhaltenen DSN zum Aufbau einer Datenbankverbindung zu verwenden. Schlägt dieser Versuch fehl, wird der Benutzer über den Fehler informiert. Die verwendeten Variablen werden zurückgesetzt und der Test am Ende lässt die Schleife erneut durchlaufen. Im nächsten Durchlauf wird der Anwender nun auf jeden Fall nach dem DSN gefragt.
Erreicht die Schleife ihr Ende, bedeutet dies den ersten Schritt zum eigenen GP-Blog: Die Verbindung zur Datenbank wurde erfolgreich hergestellt, was mit einer Erfolgsmeldung angezeigt wird.
Das Schema einlesen
Die nächsten Dialoge haben wir ein wenig interessanter gestaltet, um Ihnen ein paar mehr Möglichkeiten der Konsolen-Klassen zu zeigen.
$q = ezcConsoleQuestionDialog::YesNoQuestion(
$out,
'Do you want to load the GP-Blog schema into
this database now?',
'y'
);
switch ( ezcConsoleDialogViewer::displayDialog( $q ) )
{
case 'y':
try
{
$schema = ezcDbSchema::createFromFile(
'xml',
dirname( __FILE__ ) . '/schema.xml'
);
$schema->writeToDb( $db );
}
catch ( Exception $e )
{
$out->outputLine(
'Could not write schema to database. Reason',
'error'
);
$out->outputLine( $e->getMessage(), 'error' );
die();
}
$out->outputLine(
'Successfully written schema to database.',
'success'
);
break;
case 'n':
$q->reset();
$q->options->text = 'Is the schema already
loaded correctly?';
if ( ezcConsoleDialogViewer::displayDialog( $q )
=== 'n' )
{
$out->outputLine(
'Please make sure to install the schema
correctly. Either manually or using this
installer. Thanks!',
'error'
);
die();
}
break;
}Listing 18.15 Eine komplexere Dialogstruktur zum Installieren des Schemas
Wir wollen dem Benutzer die Wahl lassen, das Schema des GP-Blogs jetzt zu installieren oder dies manuell zu tun. Zu diesem Zweck wird eine vorgefertigte Ja-Nein-Frage verwendet, welche die Dialogklasse mit der statischen Methode YesNoQuestion() bereitstellt. Der von dieser Methode erzeugte Fragedialog akzeptiert als Antwort die Zeichen y, Y, n oder N. Alle Antworten werden per Einstellung des Prüferobjekts in Kleinbuchstaben umgewandelt, sodass Sie lediglich auf zwei Zeichen prüfen müssen. Im Text fragen wir den Benutzer, ob er die Datenbankstruktur nun anlegen möchte oder ob er dies manuell getan hat.
Erneut kommt die Helfermethode displayDialog() zum Einsatz, die solange den Dialog anzeigt, bis der Benutzer eine gültige Antwort eingegeben hat. Das switch-Statement testet also nur auf y oder n und reagiert entsprechend.
Hat der Benutzer Ja gewählt, so wird die Datenbankstruktur, die Sie in Abschnitt 18.2.1, »Das aktuelle Schema auslesen«, unter setup/schema.xml gespeichert haben, ausgelesen und das ezcSchema-Objekt wird wieder hergestellt. Anschließend wird das Schema in die Datenbank geschrieben. Tritt hierbei ein Fehler auf, so wird der Benutzer informiert und das Programm beendet. Verläuft das Schreiben des Schemas problemlos, beantwortet das Programm dies durch eine Erfolgsmeldung und beendet das switch-Statement.
Im Fall, dass der Benutzer die Frage nach der Installation des Schemas mit Nein beantwortet hat, überprüft das Programm, dass der Anwender das Datenbankschema tatsächlich installiert hat. Die so eben erstellte Ja-Nein-Frage wird hierbei erneut verwendet (reset()) und mit einem neuen Text versehen. Verneint der Anwender die Installation des Schemas, beendet sich das Programm wie im Fehlerfall.
Im Anschluss an das switch-Statement dieses Codeauschnitts können Sie also ausreichend sicher sein, eine korrekt installierte Datenbank vorzufinden.
Einstellungen speichern
Im nächsten Schritt wird die Einstellung des DSN im GP-Blog gespeichert. Die Werkzeuge dazu kennen Sie bereits aus Kapitel 5, »Konfiguration«.
$cfg->setSetting(
'installation',
'dsn',
$dsn,
'Set by setup tool.'
);
$q->reset();
$q->options->text = 'Do you want to set the blog
to debugging mode?';
$q->options->validator->default = 'n';
$cfg->setSetting(
'devel',
'debug',
( ezcConsoleDialogViewer::displayDialog( $q ) === 'y' ),
'Set by setup tool.'
);
try
{
$iniWriter = new ezcConfigurationIniWriter();
$iniWriter->init(
dirname( __FILE__ ) . '/../config',
'site',
$cfg
);
$iniWriter->save();
}
catch ( Exception $e )
{
$out->outputLine(
'Failed to write configuration. Reason:',
'error'
);
$out->outputLine( $e->getMessage(), 'error' );
die();
}
$out->outputLine(
'Your GP-Blog is now installed correctly.',
'success'
);
$out->outputLine();
$out->outputLine(
'Thank you for using the GP-Blog installer!'
);
$out->outputLine(
'And with this, we are reaching... the end...'
);Listing 18.16 Die Konfiguration speichern und das Programm beenden
Das zu Anfang erzeugte Konfigurationsobjekt wird nun mit der neuen Einstellung zum DSN ausgerüstet. Anschließend wird erneut die Ja-Nein-Frage mit dem Aufruf von reset()verwendet, um den Benutzer zu fragen, ob er den Debugging-Modus einstellen möchte. Die entsprechende Antwort wird ebenfalls in das Konfigurationsobjekt eingetragen.
Am Ende des Setup-Programms wird mit Hilfe von ezcConfigurationIniWriter versucht, die neu ausgestattete Konfiguration zu schreiben. Gelingt dies, »verabschiedet« sich das Programm mit einer letzten Erfolgsmeldung und damit auch das gesamte GP-Blog von Ihnen. – Denn dies ist das letzte Beispiel in Zusammenhang mit unserer Beispielapplikation.





Ihre Meinung






