RESTful Webservices (2): Einstieg TYPO3 Flow

RESTful Webservices (2): Einstieg mit TYPO3 Flow

Während ich letztens bereits ein paar Worte über die grundlegende Funktionsweise von REST-Webservices geschrieben hatte, geht es heute wirklich ans Eingemachte: In einem praktischen Beispiel geht es in diesem Blog-Beitrag um die Umsetzung solcher Webservices mit TYPO3 Flow.

  1. Bevor es losgeht …
  2. Kickstarten eines Pakets
  3. Verwendung des RestControllers
  4. Verwendung des JsonViews
  5. Erstellen neuer Objekte
  6. Zwischendurch: Routing
  7. Bearbeiten und Löschen von Artikeln
  8. Ausblick

Bevor es losgeht …

Um nicht ganz bei Null anfangen zu müssen, setze ich für diesen Artikel einige Grundkenntnisse in der Arbeit mit TYPO3 Flow voraus.

Mein letzter Blog-Artikel zum Thema ist zwar schon ein paar Tage her, aber noch weitgehend aktuell (alternativ gibt es auch in der aktuellen Ausgabe des t3n Magazins einen TYPO3 Flow-Grundlagenartikel von mir ;-)).

typo3 schulungen

Eins vorab: RESTful Webservices mit TYPO3 Flow zu entwickeln, ist auch für mich Neuland. Falls euch beim Lesen für ein bestimmtes Problem eine bessere Lösung als meine einfällt, hinterlasst gerne einen Kommentar oder einen Pull-Request auf Github.

Kickstarten eines Pakets

In meinem letzten Artikel hatte ich eine einfache Artikel-Verwaltung als Beispiel für einen REST-Webservice herangezogen. Um diesem Beispiel treu zu bleiben, kickstarten wir zunächst ein TYPO3 Flow-Paket, welches ein Domain Model und Repositories für eine entsprechende Artikel-Verwaltung enthält:

Diese drei Befehle erstellen ein neues TYPO3 Flow-Paket mit dem Namen Mw.RestExample sowie ein dazugehöriges Article-Model. Um die Sache zunächst nicht unnötig zu verkomplizieren, enthält das Domain Model nur eine einzige Klasse Article mit den Attributen name und price.

Anschließend müssen noch die benötigten Datenbank-Tabellen erstellt werden. Normalerweise solltet ihr hier natürlich mit Migrations arbeiten. Der Bequemlichkeit halber verwende hier das einfachere doctrine:update:

Verwendung des RestControllers

Wie euch vielleicht aufgefallen ist, habe ich oben noch keinen Controller für das Paket erstellt. Dies liegt daran, dass Ihr für einen REST-Webservice keinen gewöhnlichen ActionController braucht (der normalerweise vom Kickstarter angelegt werden würde), sondern einen speziellen RestController.

Legt also einen neuen ArticleController in der Datei Classes/Mw/RestExample/Controller/ArticleController.php an, der von der RestController-Klasse erbt:

Der RestController hat allerdings eine Besonderheit: Der normale ActionController erwartet die auszuführende Action als URL-Parameter; das Standard-Routing folgt dem Schema http://<hostname>/<package-key>/<controller>/<action>. Der RestController versucht hingegen, den Namen der Action-Methode aus der verwendeten HTTP-Request-Methode zu ermitteln. Die Zuordnung von Request-Methode auf Action-Namen läuft dabei wie folgt:

Dies sorgt dafür, dass beispielsweise bei einem GET-Request auf /article die listAction, bei einem GET-Request auf /article/1234 die showAction und bei einem POST-Request auf /article die createAction aufgerufen wird.

Verwendung des JsonViews

Als Datenaustauschformat soll zunächst einmal das JSON-Format dienen (später werden wir den Webservice dann so umbauen, dass das Format per Content Negotiation dynamisch ausgehandelt wird). Auch hierzu bietet TYPO3 Flow bereits eine fertige Klasse, den JsonView. Durch wenige Zeilen im Controller könnt Ihr den Fluid-View (oder genauer gesagt die Klasse TYPO3\Fluid\View\TemplateView), die normalerweise genutzt wird, mit dem JsonView ersetzen:

Achtet beim Klassennamen vor allem auf die doppelten Backslashes (da der Backslash auch ein PHP-Sonderzeichen ist, muss er nochmal gesondert escaped werden).

Anschließend könnt Ihr euren Webservice bereits mit einem einfachen Kommandozeilenaufruf testen:

Zugegeben, die Ausgabe ist noch nicht besonders überzeugend, aber immerhin gibt es ja auch noch gar keine Artikel, die ausgegeben werden könnten. Somit gibt der Controller korrekterweise einfach eine leere Liste aus (in JSO-Notation [] geschrieben).

Erstellen neuer Objekte

Es wird also Zeit, dem Webservice beizubringen, wie er neue Objekte erstellen kann. Oben hatte ich ja bereits dargestellt, dass POST-Operationen stets auf die Methode createAction gemappt werden. Fügt dem Controller also diese Methode hinzu:

Die Zeile, in der das Attribut $resourceArgumentName genannt wird, ist eine weitere Eigenart des RestControllers. Sie ist notwendig, damit ein POST-Request, der ein Objekt mit dem Namen „article“ enthält, automatisch auf die createAction-Methode abgebildet wird. Über den Aufruf $this->response->setStatus(201) kann zudem der Antwort-Statuscode geändert werden, sodass TYPO3 Flow nicht mit dem Status „200 OK“, sondern „201 Created“ antwortet.

Zeit, das Ganze zu testen. Erstellt zunächst ein neues Objekt per POST-Request an den Controller:

Im Moment versteht der RestController nur Objekte in URL-codierter Form. Wie Ihr Flow davon überzeugen könnt, auch JSON- oder XML-codierte Objekte zu akzeptieren, betrachte ich später.

Die Antwort enthält hier nur den Wert null. Aber in der createAction-Methode wurden dem View ja auch keine Daten zugewiesen, deshalb wird auch nichts ausgegeben. Aber gut erkennbar ist, dass TYPO3 Flow mit dem korrekten Statuscode antwortet (der Parameter -D - veranlasst cURL dazu, die Antwort-Header auf der Standardausgabe darzustellen).

Versuchen wir nun erneut, alle Artikel zu laden:

Nun wird auch deutlich, wie der JsonView genau funktioniert: Wird dem JsonView ein Objekt übergeben, ruft der View einfach alle Getter-Methoden des Objekts auf und baut daraus ein JSON-Objekt zusammen (zur Erinnerung: Die Klasse Article enthält die Methoden getName() und getPrice(); auch das aus dieser Klasse generierte JSON-Objekt enthält die Schlüssel name und price).

Zwischendurch: Routing

Bevor Ihr euch darum kümmern könnt, Artikel zu bearbeiten und zu löschen, sollte über die Routing-Konfiguration sichergestellt sein, dass die entsprechenden Aktionen auch über entsprechende Routen erreichbar sind. Hierzu könnt Ihr in Eurem Paket in der Datei Configuration/Routes.yaml entsprechende Routen hinterlegen:

Damit diese Routen greifen, müssen sie allerdings auch in der globalen Routing-Konfiguration in der Datei /Configuration/Routes.yaml eingetragen werden:

Überprüfen könnt Ihr die Routing-Konfiguration mit dem routing:list-Befehl:

Ein cURL-Request auf die neue URI liefert abschließende Gewissheit:

Bearbeiten und Löschen von Artikeln

Nun könnt Ihr die noch fehlenden Controller-Aktionen implementieren:

Beim Bearbeiten eines Artikels fällt allerdings schnell auf: Um per PUT-Request einen Artikel ändern zu können, muss dessen URI bekannt sein. Idealerweise sollte also beim Aufruf der listAction zusätzlich zu Name und Preis jedes Artikels dessen URI dargestellt werden. Hierzu kann das Article-Model entsprechend modifiziert werden:

Hier wird zunächst die BaseUri aus der Flow-Konfiguration ausgelesen (diese kann über die Settings.yaml gesetzt werden). Das Pfad-Schema ist im obigen Beispiel noch hardcodiert, sollte später aber natürlich auch in eine Konfigurationsdatei ausgelagert werden.

Bei einem erneuten Aufruf der listAction seht Ihr die URIs der angelegten Artikel:

Da die URI des Artikels nun bekannt ist, können wir jetzt versuchen, diesen Artikel zu bearbeiten:

Anschließend zeigt ein GET-Request an dieselbe URI, dass Flow den Artikel entsprechend aktualisiert hat:

Ausblick

Der Flow-Webservice kann nun Ressourcen ausliefern, außerdem kann der Benutzer über entsprechende HTTP-Aufrufe Ressourcen anlegen, bearbeiten und löschen. Das ist schonmal großartig, aber ein paar offene Punkte bleiben noch zu erledigen; denen widme ich mich dann später in einem eigenen (vielleicht auch mehreren) Blog-Artikel(n):

  • Der Webservice soll sein Datenaustauschformat mit dem Client dynamisch via Content Negotiation aushandeln.
  • Es sollen auch JSON- und XML-codierte Eingaben entgegen genommen werden.
  • Es wird eine Authentifizierung benötigt, damit nicht die ganze Welt auf den Webservice zugreifen darf.
  • Um den Zugriff zu beschleunigen, könnten GET-Zugriffe von einem Reverse-Proxy gecached werden.

Den Quelltext des für diesen Artikel entwickelten Flow-Pakets findet Ihr übrigens GPL-lizensiert auf Github.

Sprechblase
Falls Ihr Anregungen oder Verbesserungsvorschläge habt, schickt mir gerne einfach einen Pull-Request (oder hinterlasst hier einen Kommentar ;-)).

 

Kommentare

  1. Alexander am

    Hallo Martin,

    wie sieht es mit der Authentifizierung aus?
    Ansonsten funktioniert alles super.

    Viele Grüße,
    Alexander

    Antworten
  2. Martin Tepper am

    Wirklich guter Artikel. Gibt leider nur noch selten solche guten Tutorials.
    Die Umsetzung verlief reibungslos mit TYPO3 Flow 2.0.

    Antworten
  3. Michael Knoll am

    Super Tutorial, vielen Dank. Hat mir gestern sehr geholfen!

    Ich möchte an dieser Stelle noch auf die PHP-Library Guzzle hinweisen, die das Schreiben der Client-Seite sehr vereinfacht:

    https://github.com/guzzle/guzzle

    Viele Grüße,

    Michael

    Antworten
  4. Soeren Kroell am

    Super Artikel, genau das was ich gesucht habe!!!!!

    Antworten
  5. Martin Keck am

    Kleiner Hinweis: Die JsonView-Konfiguration von Flow 2.0 ist noch nicht ganz ausgereift, wie ich beim Versuch, eine verschachtelte Objekt-Hierarchie als JSON auszugeben, feststellen musste. Ein entsprechender Patch ist aber bereits Under Review:

    Antworten
  6. Martin Keck am

    Toller Artikel. Wann gehts weiter?

    Antworten

Kommentar hinzufügen