Lernziele

  • Einfache Webanwendung mit dem Symfony-Framework implementieren können
  • Die grundlegenden Konzepte des Model-View-Controller-Stils richtig anwenden können
  • Routing-Regeln festlegen können
  • Einfaches Model mit Doctrine auf Datenbank abbilden können
  • Rollen- und Attributbasierte Zugriffskontrolle umsetzen können

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:

  1. Setup
  2. Creating Pages
  3. Routing
  4. Controller
  5. Templates
  6. 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):


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:

  1. Der Nutzer loggt sich über ein Login-Formular ein. (firewall)
  2. Die Nutzer-Daten liegen in der Datenbank; es gibt eine Entity-Klasse, die die Nutzer repräsentiert. (provider)
  3. 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):


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):


Bewertungskriterien (max. 11 Punkte)

  • Basisfunktionalität ist implementiert (4 Punkte)
  • Sinnvolle Strukturierung des Codes in Model, View, und Controller (2 Punkte)
  • Grundlegende Validierung
  • Erweiterte Validierung
  • Authentisierung über Login-Formular, gegen Datenbank, mit verschlüsselten Passwörtern
  • Rollenbasierte Authorisierung (Unterscheidung eingeloggt/nicht eingeloggt)
  • Attributbasierte Authorisierung (Unterscheidung eigenes/fremdes Lauftagebuch)