Kontext
In der vorherigen Übung haben Sie ein einfaches Lauftagebuch implementiert.
Nun soll daraus eine richtige Web-Anwendung werden, die die Daten mehrerer
Benutzer verwalten kann.
Das verbesserte Lauftagebuch hat folgende Eigenschaften:
- Es soll mehrere Nutzer/innen ("Läufer") unterstützen, die sich mit
Nutzername und Passwort bei der Anwendung anmelden können.
- Jede/r Läufer/in hat ein eigenes Lauftagebuch mit Einträgen der
bekannten Art:
- Datum, gelaufene Strecke und gelaufene Zeit
- Die Daten sollen in einer (SQLite-)Datenbank gespeichert werden.
Allgemeine Hinweise:
- Deployen Sie Anwendung auf Heroku; hinterlegen Sie Ihr Projekt auf dem
f4
-Server.
- Nutzen Sie Symfony in Version 4, z.B. so:
$> composer create-project symfony/skeleton uebung-6
$> cd uebung-6/
- Sie dürfen Teile Ihrer Implementierung von Übungaufgabe 5
wiederverwenden, halten Sie sich aber an die Architektur von Symfony.
- Die folgenden Symfony-Bundles dürften ausgesprochen hilfreich sein:
$> composer require twig doctrine form validator security
$> composer require --dev profiler
$> composer require sensio/framework-extra-bundle
$> composer require doctrine/doctrine-fixtures-bundle
- Sie dürfen beliebige Ihnen vertraute Frontend-Technologien einsetzen
(also CSS- und JavaScript-Bibliotheken).
- Auf GitHub finden Sie außerdem eine
Symfony-Implementierung der Supero-Webseite,
die umfassend kommentiert ist und beim Verstehen von Symfony hilfreich ist.
- Gehen Sie unbedingt schrittweise vor (1. - 4.).
Die Gesamtaufgabe ist zwar umfangreich, aber keiner der Schritte ist
sonderlich schwer, wenn Sie in die angegebenen Doku-Stellen hineinlesen.
Literatur
Einen sehr guten Einstieg in Symfony (bei dem Ihnen sehr vieles aus den
Lehrvorträgen bekannt vorkommen sollte) finden in den sechs
"Getting Started"-Kapiteln der offziellen Dokumentation:
- Setup
- Creating Pages
- Routing
- Controller
- Templates
- Configuration
Diese 6 Kapitel lassen sich tatsächlich linear lesen; aber ansonsten ist
die Symfony-Doku ein echtes Hypertext-Dokument: Sie finden an allen Stellen
Querverweise und am Ende jedes Doku-Artikels eine Liste mit verwandten
Themen.
Es lohnt sich, hier ein bisschen Zeit mit Lesen zu verbringen!
1. Basisfunktionalität
Die von Ihnen zu implementierende Symfony-Anwendung soll aus Nutzersicht
folgende Ansichten untersützen:
- Läuferübersicht:
Tabelle, die zeilenweise alle Läufer auflistet.
Die Tabelle soll folgende Spalten haben:
- Läufername (= Nutzername)
- Anzahl der Lauftage
- Gesamte bisher gelaufene Strecke
- Läuferprofil:
Beim Klick auf einen Läufernamen in der Übersicht soll man zum
Profil des Läufers gelangen.
Das Profil hat soll Folgendes anzeigen:
- Läufername
- Anzahl der Lauftage
- Gesamttage seit dem ersten Lauftag (bis heute, nicht bis zum letzten
hinterlegten Lauftag)
- Gesamte bisher gelaufene Strecke
- Tabelle mit allen Laufeinträgen, absteigend sortiert nach Datum (neue
Einträge oben).
Diese Tabelle soll die folgenden Spalten haben:
- Datum
- Gelaufene Strecke
- Gelaufene Zeit
- Durchschnittgeschwindigkeit
- In jeder Zeile soll es eine Löschfunktion geben, die den betreffenden
Laufeintrag löscht.
- Unter der Tabelle soll es ein Formular geben, um einen neuen Eintrag zum
aktuell geöffneten Läuferprofil hinzuzufügen.
Dabei werden vom Nutzer nur Datum, gelaufene Strecke und Zeit
eingegeben;
die Durchschnittsgeschwindigkeit soll die Anwendung selbst berechnen.
Sie brauchen keine "Bearbeiten"-Funktion für Laufeinträge implementieren;
"Löschen" und "Hinzufügen" reicht.
Es dürfen (anders als bei Übungsaufgabe 5) mehrere Laufeinträge eines
Nutzers für den gleichen Tag existieren.
Referenzen & Literatur
Für die Basisfunktionalität sollten Sie folgende Framework-Elemente
verwenden (hilfreiche Links zu den jeweiligen Dokus sind jeweils aufgelistet):
- Model-Klassen um die o.g. Informationen in einer Datenbank speichern
zu können.
Denken Sie daran im Model zu Modellieren: Wenn also etwas in der
echten Welt relevant ist, dann sollte es dazu eine
Eigenschaft/Methode/Annotation/... in den Model-Klassen geben!
- Je einen Controller, um die o.g. Operationen (Anzeigen, Anlegen,
Löschen) auszuführen.
- Controller müssen über eine Route erreichbar sein.
Das URL-Format ist Ihnen überlassen.
- Twig-Templates zur Realisierung der verschiedenen Ansichten, die ein
Nutzer zu sehen bekommt.
- Formular zur Erzeugung neuer Datensätze.
2. Fehlerbehandlung bei der Eingabe
Sorgen Sie dafür, dass die Formular-Eingaben serverseitig validiert werden.
Grundlegende Validierung:
Folgende falschen Eingaben sollen serverseitig erkannt werden, und jeweils
dem Nutzer als Fehlermeldung angezeigt werden:
- Falsches Datumsformat (z.B. für alte Browser, die
input type=date
nicht
kennen)
- Ungültiges Streckenformat (keine Zahl)
- Negative Strecke oder Strecke der Länge 0
- Ungültiges Zeitformat
- Negative Zeit oder Zeit der Länge 0
Erweiterte Validierung:
Folgende falsche Eingaben sollen (zusätzlich zur grundlegenden Validierung)
serverseitig erkannt werden, und ebenfalls jeweils dem Nutzer als
Fehlermeldung angezeigt werden:
- Datum in der Zukunft
- Durchschnittsgeschwindigkeit über 40 km/h
Referenzen & Literatur
Für die Validierung sollten Sie folgende Framework-Elemente verwenden
(hilfreiche Links zu den jeweiligen Dokus sind aufgelistet):
- Validierung gegen Standard-Constraints auf Grundlage von Model-Klassen
- Symfony-Doku: Validierung
- Tipp:
Denken Sie daran, dass Symfony Ihnen in vielen Fällen die Validerung
erleichtern kann, wenn Sie in Ihrer Model-Klasse bereits die
entsprechenden Constraints annotieren (Modellierung im Model).
Sie müssen die Constraints aber nicht unbedingt im Model definieren
(wie immer: Symfony ist da flexibel).
- Validierung gegen eigene Constraints
- Symfony-Doku: Eigene Validierer schreiben
- Tipp:
Um einen Klassen-Constraint in einer
Formular-Klasse anzuwenden, müssen Sie den
Constraint in der
configureOptions()
-Methode setzen
(im Ggs. zu normalen Constraints, die in der buildForm()
-Methode
gesetzt werden):
/* src/Form/MyFantasticType.php */
public class MyFantasticType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
/* ... */
}
public function configureOptions(OptionsResolver $resolver) {
/* ... */
$resolver->setDefault('constraints', [new MyCustomConstraint()]);
}
}
3. Authentisierung
Implementieren Sie eine Authentisierung mit folgenden Eigenschaften:
- Der Nutzer loggt sich über ein Login-Formular ein. (
firewall
)
- Die Nutzer-Daten liegen in der Datenbank; es gibt eine Entity-Klasse, die
die Nutzer repräsentiert. (
provider
)
- Die Nutzer-Passwörter werden in der Datenbank verschlüsselt gespeichert.
(
hashing
)
Der Login soll über die Adresse /login
erfolgen, der Logout über /logout
.
Es ist Ihnen überlassen, wohin ein Nutzer nach erfolgreichem Login bzw.
Logout weitergeleitet wird.
Tipp:
Sehr wahrscheinlich haben Sie schon in der ersten Teilaufgabe
("Basisfunktionalität") eine Entity-Klasse erstellt, die sich eignet, um sie
zu einer Nutzer-Klasse für die Authentisierung auszubauen.
Wenn Sie bei dieser Teilaufgabe eine neue Nutzer-Klasse erstellen wollen,
sollten Sie vielleicht Ihr Model aus der ersten Teilaufgabe überdenken.
Achtung:
Sie brauchen ausdrücklich keine Nutzerregistierung zu implementieren.
Stattdessen legen Sie ein paar Nutzerdaten einfach über ein Fixture an:
/* src/DataFixtures/MyUserFixture.php */
class MyUserFixture extends Fixture {
private $encoder;
public function __construct(UserPasswordEncoderInterface $encoder) {
$this->encoder = $encoder;
}
public function load(ObjectManager $manager) {
$user1 = new MyUserEntity();
$user1->setUsername('peter');
$user1->setPassword($this->encoder->encodePassword($user1, 'p4s5w0rt'));
$manager->persist($user1);
/* ... */
}
}
- Tipp:
Standardmäßig wird das für Fixtures nötige
DoctrineFixturesBundle nur für die Umgebungen
'dev'
und 'test'
aktiviert.
Wenn Sie auch in einem Produktiv-Umgebung (z.B. Ihrer Heroku-Instanz)
Fixture laden wollen, müssen Sie in der config/bundles.php
das Bundle
noch für alle Umgebungen aktivieren
(genau wie bei der Supero-Symfony-Anwendung):
--- config/bundles.php
+++ config/bundles.php
@@ -8,5 +8,5 @@ return [
Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
- Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
+ Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['all' => true],
];
- Tipp:
Der Encoder funktioniert erst, wenn Sie das Encoding in der
security.yaml
fertig eingerichtet haben.
Die Fixtures in Ihre Datenbank laden können Sie einfach von der Kommandozeile:
php bin/console doctrine:fixtures:load -n
Referenzen & Literatur
Für die Authentisierung sollten Sie folgende Framework-Elemente verwenden
(hilfreiche Links zu den jeweiligen Dokus sind aufgelistet):
- Authentisierung gegen eine Entity-Klasse (d.h. gegen eine
Model-Klasse, die vom ORM-Mapper in der Datenbank gespeichert wird).
- Symfony-Doku: Login-Formulare
- Symfony-Doku: Authentisierung über Entity-Provider
- Achtung:
Auch wenn in der Symfony-Doku das "FOSUserBundle" beworben wird,
nutzen Sie dies nicht!
Zum einen brauchen Sie nur einen Bruchteil vom FOSUserBundle und
dessen Einbindung wäre damit aufwändiger, als Ihre bisherige
Symfony-App um wenige Zeilen zu erweitern.
Zum anderen ist das FOSUserBundle ohnehin noch nicht mit Symfony 4
kompatibel.
- Fixtures, um einen Basis-Datensatz von Nutzern zu erzeugen
4. Autorisierung
Verwenden Sie rollenbasierte Zugriffskontrolle um Folgendes zu
realisieren:
- Eingeloggten Nutzern soll ein "Logout"-Link angezeigt werden, allen
anderen ein "Login"-Link.
- Nur eingeloggte Nutzer sollen in den Läuferprofilen die Gesamttage seit
dem ersten Lauftag, sowie die Laufzeiten und Durchschnittsgeschwindigkeiten
sehen.
Verwenden Sie attributbasierte Zugriffskontrolle um Folgendes zu
realisieren:
- Eingeloggte Nutzer sollen nur auf ihrem eigenen Läuferprofil die
Lösch-Links und das Eingabeformular für weitere Einträge sehen.
(Anonyme Nutzer sollen diese Dinge nie sehen.)
- Auch serverseitig soll ein Request zum Löschen oder Anlegen von Einträgen
nur für die Nutzer möglich sein, deren Tagebuch bearbeitet werden soll.
Fremde Lauftagebücher sollen zwar angesehen, aber nicht bearbeitet werden
dürfen.
(Anonyme Nutzer dürfen gar nichts beabeiten.)
Referenzen & Literatur
Für die Autorisierung sollten Sie folgende Framework-Elemente verwenden
(hilfreiche Links zu den jeweiligen Dokus sind aufgelistet):
- Rollenbasierte Zugriffskontrolle
- Attributbasierte Zugriffskontrolle