RESTful Webservices (2): Einstieg mit TYPO3 Flow
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 ;-)).
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.
Kommentare
Hallo Martin,
wie sieht es mit der Authentifizierung aus?
Ansonsten funktioniert alles super.
Viele Grüße,
Alexander
Wirklich guter Artikel. Gibt leider nur noch selten solche guten Tutorials.
Die Umsetzung verlief reibungslos mit TYPO3 Flow 2.0.
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
Super Artikel, genau das was ich gesucht habe!!!!!
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:
Toller Artikel. Wann gehts weiter?