RESTful Symfony

RESTful-APIs mit Symfony2 realisieren
Kommentare

Wer zum Beispiel Apps entwickelt und diese an eine Symfony-Applikation anbinden möchte, muss eine Schnittstelle bereitstellen. Vieles spricht dafür, ein REST-API zu implementieren. Dieses Tutorial zeigt, wieso.

Vor allen Dingen ist eine RESTful-API sehr schlank und auf Server- sowie Clientseite leichter umzusetzen als zum Beispiel eine Schnittstelle auf SOAP- oder XML-RPC-Basis. Ein weiterer Vorteil ist, dass eine RESTful-API auch mit JSON arbeiten kann. Auch wenn dadurch nicht alle Anforderungen des Richardson Maturity Models erfüllt werden, bietet JSON einige Vorteile. So wird in nativen Apps kein vollständiger XML-Parser, sondern nur eine vergleichsweise schlanke JSON- Bibliothek benötigt. Außerdem ist REST sehr elegant, denn es nutzt die HTTP-Methoden (in diesem Zusammenhang auch als Verben bezeichnet) und HTTP-Statuscodes voll aus, statt lediglich alle Daten über POST-Requests an den gleichen Endpunkt zu senden. Das fühlt sich nicht nur viel besser an, es hat auch direkte Auswirkungen in der Praxis. So könnte zum Beispiel ein Cache wie Varnish keinen der API-Calls zu einer SOAP- oder XMLRPC-Schnittstelle behandeln, da bei POST-Requests davon auszugehen ist, dass Seiteneffekte wie eine Veränderung von Daten zu erwarten sind.

Der Cache kann nicht unterscheiden, ob wir nur Daten abfragen oder auch verändern. Das macht Caching unmöglich. Anders herum ausgedrückt, kann ein RESTful-API bei Bedarf auch von einem Cache optimiert und beschleunigt werden. Mit dem FOSRestBundle steht eine Lösung für Symfony bereit, mit der REST-APIs sowohl für neue als auch bestehende Applikationen sehr einfach, schnell und sauber implementiert werden können.

Das FOSRestBundle wird von den FriendsOfSymfony –einem Zusammenschluss verschiedener Symfony-Entwickler, gepflegt. Es arbeitet stabil und wurde bisher bereits mehr als 100.000 mal installiert. Das FOSRestBundle selbst erzeugt nach erfolgter Installation noch kein API. Es bringt viel mehr Lösungen und Werkzeuge für verschiedene Aufgaben und Probleme, die einem Symfony-Entwickler bei der Implementierung einer REST-Schnittstelle begegnen, mit. Dazu gehört zum Beispiel ein spezieller Controller, der vom Symfony Standard Controller erbt und einige sehr praktische Methoden hinzufügt. Zudem spart auch die automatische Definition von Symfony-Routingeinträgen Zeit und Konfigurationsarbeit. Man kann ein REST-API auch nur mit der Hilfe der Symfony-Bordmittel realisieren. Dies ist jedoch umständlicher und kostet mehr Zeit.

RESTFul-APIs

Die Abkürzung REST steht für Representational State Transfer. Es ist kein eigener Standard oder Protokoll. Vereinfacht dargestellt beschreibt REST stattdessen, wie bereits vorhandene Webstandards, also das HTTP-Protokoll, in einer dem Web gerechten Weise verwendet werden können. Die ursprüngliche Definition von Roy Fieldings bezieht sich dabei nicht explizit auf HTTP, jedoch werden die von ihm aufgestellten Anforderungen optimal durch HTTP gelöst. Dies ist nur bedingt verwunderlich, wenn man bedenkt, dass Roy Thomas Fielding – geistiger Vater der REST-Idee – einer der leitenden Autoren der ursprünglichen HTTP-Spezifikation sowie Mitgründer des Apache-Webserver-Projekts ist. Zudem hat er bei der Entwicklung von HTML mitgewirkt. REST ist ein Paradigma. Folgt man ihm, wird ein API als RESTful eingestuft.

Ein wichtiges Merkmal eines RESTful-API ist, dass jede Ressource über einen eigenen einzigartigen URL angesprochen werden kann. Das Richardson Maturity Model, das eine Skala für die REST-Konformität festlegt, legt dies als Anforderung für eine Kompatibilität gemäß Level1 fest.

Level2 wird erreicht, wenn die Aktionen implizit über HTTP-Verben signalisiert werden. Das bedeutet, dass die HTTP-Methode festlegt, welche Aktion gewünscht ist, nicht etwa der URL. Den vier grundlegenden Aktionen werden die folgenden Verben bzw. Methoden zugeordnet:

  • Create = POST
  • Read = GET
  • Update = PUT
  • Delete = DELETE

Um das Level 3 gemäß Richardson zu erreichen, muss das API über Hypermedia Controls, bei HTML also zum Beispiel Hyperlinks, navigierbar sein. Der Vorteil dabei ist in erster Linie die gleichzeitige Funktion als Dokumentation. Ein Entwickler kann sich daran viel einfacher orientieren als an einer langen Dokumentation. Die Beispielanwendung in diesem Artikel berücksichtigt jedoch nur das Level 2, da ansonsten der Rahmen gesprengt würde.

Vorbereitung und Hinweise

In diesem Artikel wird die Implementierung eines Beispiel-API beschrieben, mit dem eine Buchsammlung rudimentär verwaltet werden kann. In der hier gezeigten Applikation gibt es, um den Rahmen nicht zu sprengen, nur die Entität Buch. In einer realen Anwendung würden natürlich die Datenbank sowie die Schnittstelle komplexer. So kann zum Beispiel ein Buch mehrere Autoren haben, müsste eine ISBN-Nummer speichern und ähnliches. Zudem wäre es sinnvoll, Bücher zu kategorisieren oder Schlagwörter zu vergeben.

Es wird außerdem, um die gezeigten Beispiele nachvollziehen zu können, eine Entwicklungsumgebung benötigt. Die Installation einer Symfony-Applikation wird in der offiziellen Dokumentation beschrieben, weshalb ich auf diesen Schritt nicht weiter eingehe.

Für Tests des API wird während der Entwicklung ein HTTP-Client benötigt. Sehr gut funktioniert für diese Zwecke der Firefox-REST-Client, der plattformunabhängig als Erweiterung für den Firefox-Browser zur Verfügung steht.

RestBundle installieren

Installieren wir also das FOSRestBundle sowie das von den FriendsOfSymfony empfohlene JMSSerializerBundle. Die Beispiele in diesem Artikel gehen davon aus, dass Composer in der Entwicklungsumgebung zur Verfügung steht. Dies ist ein Werkzeug zur Verwaltung von Abhängigkeiten. Hinweise zur Einrichtung findet man im offiziellen Symfony-Handbuch.

Mit folgenden Aufrufen des Composers (Terminal bzw. Shell) im Hauptverzeichnis des Projekts wird die Installation durchgeführt. Bitte die hier gezeigten Versionsnummern beachten. Diese sind zum aktuellen Zeitpunkt (Stand 03.01.2013) die neueste als stabil gekennzeichnete Version. Wird stattdessen der aktuelle Entwicklungszweig verwendet, kann es naturgemäß durch Veränderungen oder Bugs hier und da zu kleineren Schwierigkeiten oder Problemen kommen.

$ php composer.phar require friendsofsymfony/rest-bundle 0.10.0
$ php composer.phar require jms/serializer-bundle 0.9

Zusätzlich müssen die soeben installierten „Pakete“ noch im Kernel (Datei app/AppKernel.php) aktiviert werden. Dazu wird im $bundles-Array eine Instanz der beiden Bundle-Klassen erzeugt:

$bundles = array(
  // ...
  new JMSSerializerBundleJMSSerializerBundle($this),
  new FOSRestBundleFOSRestBundle(),
);

Um den Response Listener, den Teil des FOSRestBundle, der sich um die Rückgabewerte kümmert, zu konfigurieren, müssen in der Datei app/config/config.yml noch folgende Angaben gemacht werden. Ohne diese Konfiguration funktioniert die automatische Serialisierung der Daten nicht. Symfony sucht dann weiterhin nach einem Template für die ausgeführte Action, das jedoch nicht gefunden wird (Listing 1).

Listing 1

sensio_framework_extra:
view:
  annotations: false

fos_rest:
  param_fetcher_listener: true
  body_listener: true
  format_listener: true
view:
  view_response_listener: 'force'
routing_loader:
  default_format: json

Wurde auch dies abgeschlossen, müssen in den nächsten Schritten in einem noch zu erstellenden Symfony-Bundle ein Controller, passende Actions, eine Entität für Bücher und weiteres erzeugt werden. Da die Beispiele in diesem Artikel mit einer Datenbank arbeiten, muss zuvor die Anwendung für den Datenbankzugriff konfiguriert werden. In der Datei app/config/parameters.yml werden die Zugangsdaten hinterlegt. Zu kontrollieren sind alle Parameter, die mit database_ beginnen.

Läuft beispielsweise ein MySQL-Server auf dem lokalen System, müssen nur noch der Name der Datenbank, der Benutzername sowie das Passwort gesetzt werden (Listing 2).

Listing 2

# app/config/parameters.yml
parameters:
  database_driver: pdo_mysql
  database_host: 127.0.0.1
  database_port: ~
  database_name: phpmagazin_restapi
  database_user: apiexample
  database_password: ***

Weiter geht es mit der Einrichtung eines eigenen Bundles für das REST-API.

Eigenes Bundle vorbereiten

Generell muss die Schnittstelle natürlich nicht in einem eigenen Bundle implementiert werden. Wer eine Applikation für den Livebetrieb entwickelt und Entitäten, Formulare sowie anderen Quellcode ohnehin in Bundles hinterlegt, fügt die API-Implementierung der entsprechenden Ressource natürlich im thematisch passenden Bundle hinzu. Dazu wird jeweils ein spezieller Controller angelegt, genau wie in diesem Artikel.

In diesem Artikel wird jedoch, da ohnehin in einer leeren Entwicklungsumgebung noch kein Platz vorhanden ist, an dem die Schnittstelle hinterlegt werden könnte, mit einem einzigen Bundle für unseren eigenen Quellcode gearbeitet. Das Bundle liegt im Namespace PhpmagazinRestBundle. Die Grundstruktur kann wie unten gezeigt erzeugt werden. Die abgefragten Werte können dabei, abgesehen von den folgenden Werten, auf den in eckigen Klammern genannten Standardwerten belassen werden:

  • Bundle namespace: Phpmagazin/RestBundle
  • Configuration format: yml

 So wird der Namespace auf PhpmagazinRestBundle geändert und als Konfigurationstyp Yaml festgelegt. Zu beachten ist, dass statt einem Backslash explizit ein Slash verwendet wird. Hintergrund ist, dass der Backslash auf der Shell eine Sonderfunktion hat und zum Entwerten (Escapen) von Sonderzeichen verwendet wird. Um Probleme zu vermeiden wird er, wie hier gezeigt, ersetzt:

$ php app/console generate:bundle
Bundle namespace: Phpmagazin/RestBundle
Configuration format (yml, xml, php, or annotation) [annotation]: yml

Wenn kein Fehler ausgegeben wurde, sollte nun unterhalb des src-Verzeichnisses ein neues Unterverzeichnis Phpmagazin existieren. In diesem liegt das soeben erzeugte Bundle mit dem Namen RestBundle.

Entität für Bücher erzeugen

Nun wird eine Entität Book erzeugt. Mittels Doctrine wird zudem eine Tabelle in der Datenbank generiert, in der die Büchersammlung gespeichert werden kann. Dazu muss im RestBundle-Verzeichnis (src/Phpmagazin/RestBundle) das Verzeichnis Entity angelegt werden. Darin gespeichert wird die Datei Book.php mit dem Inhalt aus Listing 3.

Listing 3

Die Klasse wurde mit Absicht übersichtlich gehalten und definiert nur drei Attribute: eine eindeutige ID für Bücher, einen Buchtitel und den Autor des Buches. Um von „außen“ auf die als protected gekennzeichneten Variablen zugreifen zu können, fehlen getX()– und setX()-Methoden. Doctrine bietet die Möglichkeit, diese automatisch zu generieren. Folgender Befehl erledigt dies:

$ php app/console doctrine:generate:entities 

Phpmagazin/RestBundle/Entity/Book

Fertig. Jetzt fehlt noch die Tabelle in der Datenbank. Mittels Doctrine wird diese ebenfalls über die Kommandozeile erzeugt. Die Spezifikation dafür wird aus den Annotationen (die Kommentare mit @ORM) gelesen:

$ php app/console doctrine:schema:update --force

Doctrine sollte nun Erfolg vermelden. Die Ausgabe sollte in etwa wie auf Abbildung 1 aussehen.

Abb. 1: Automatisches Schema Update mittels Doctrine

Abb. 1: Automatisches Schema Update mittels Doctrine

Bevor weitere Schritte folgen, macht es nun Sinn, die Existenz der Tabelle book zu kontrollieren. Ist sie vorhanden, hat alles funktioniert. Bei der Gelegenheit sollten dort einige Datensätze für Tests angelegt werden.

Controller und erste Action anlegen

Symfony basiert auf dem Modell- View-Controller-(MVC-)Paradigma. Um HTTP-Requests zu verarbeiten, wird daher ein Controller benötigt. Im MVC-Kontext betrachtet, ist der Controller die Schnittstelle zwischen Modell und View, sprich zwischen Datenquellen und Businesslogik auf der einen und der Darstellungsebene auf der anderen Seite. Der Controller, der im folgenden Abschnitt angelegt wird, wird in der Beispielanwendung alle für die Schnittstelle relevanten Actions enthalten. Dies sind Handler-Methoden, die eingehende HTTP-Anfragen einer bestimmten Ressource (URL) verarbeiten. In einer Action werden zum Beispiel übergebene Parameter, wie die ID einer Entität, ausgelesen, die Entität geladen und an die View-Ebene zur Darstellung weitergereicht.

Der Controller für das REST-API in der Beispielanwendung ist der BookController. Um ihn zu erzeugen, wird eine Datei mit dem Namen BookController.php im Controller-Verzeichnis des zuvor erzeugten RestBundle angelegt (Listing 4).

Listing 4

getDoctrine()
      ->getRepository('PhpmagazinRestBundle:Book')
      ->findAll();

    return array('books' => $books);
  }
}

Der Controller implementiert das ClassResourceInterface. Dadurch wird die Namensgebung für Actions etwas vereinfacht. So muss im Controller nicht bei jeder Action explizit Book bzw. Books im Methodennamen angegeben werden, also zum Beispiel getBooksAction(), newBooksAction() und so weiter. Die hier gezeigte Methode cgetAction() ist eine weitere Besonderheit des ClassResourceInterface mit spezieller Bedeutung. Da getBookAction($id) und getBooksAction() beide mit vereinfachtem Namen getAction() heißen müssten, wurde festgelegt, die Methode zum Abrufen aller Datensätze einer Entität cgetAction() zu benennen. Das c steht hier für collective.

Routing definieren

Das FOSRestBundle erstellt die Symfony-Routen nahezu automatisch. Es muss nicht jede Action explizit einem URL oder einem URL-Muster zugewiesen werden. Es reicht aus, alle REST Controller explizit zu definieren. Da die Methodenamen an einige Vorgaben gebunden sind, werden automatisch Routing-Einträge für den Controller beziehungsweise für die enthaltenen Actions angelegt. Dazu wird die Deklaration eines Controller als RESTful direkt im jeweiligen Bundle hinterlegt. Diese Konfigurationsdatei muss dann anschließend in der globalen Routingkonfiguration importiert werden.

Zunächst die Konfiguration des Controllers im RestBundle-Verzeichnis. Dort wird sie in der Datei Resources/config/routing.yml gespeichert:

# Resources/config/routing.yml
books:
type: rest
resource: PhpmagazinRestBundleControllerBookController

Damit diese Konfigurationsdatei korrekt eingelesen wird, muss sie, wie oben erwähnt, importiert werden. Dazu wird die allgemeine Routing-Konfiguration angepasst. Sie ist im Hauptverzeichnis in der Datei app/config/routing.yml zu finden:

# app/config/routing.yml
phpmagazin:
type: rest
resource: @PhpmagazinRestBundle/Resources/config/routing.yml

Das API ist nun grundlegend einsatzbereit. Wurden bereits Testdatensätze in der Tabelle book eingefügt, können diese bereits über den Basis-URL + /books (Basis-URL abhängig von der eigenen Testumgebung) abgerufen werden. Wurden noch keine Datensätze hinterlegt, wird lediglich ein leeres Array ausgegeben. Dargestellt werden die Daten im JSON-Format. Das kann dann zum Beispiel wie in Abbildung 2 aussehen.

Abb. 2: Ausgabe der Buchliste im JSON-Format

Abb. 2: Ausgabe der Buchliste im JSON-Format

Einzelne Datensätze auslesen

Ein REST-API, mit dem ausschließlich alle vorhandenen Datensätze einer Entität ausgelesen werden können, wäre nur bedingt hilfreich. Darum werden nun sukzessiv Methoden angelegt, mit denen die restliche CRUD-Funktionalität (Create, Read, Update, Delete) abgebildet wird.

Soll explizit auf eine Entität zugegriffen werden, hier also ein Buch, muss die ID der Entität übergeben und in der Datenbank ein passender Eintrag gesucht werden. Eine entsprechende Action, mit der ein bestimmtes Buch ausgelesen werden kann, könnte wie in Listing 5 aussehen. Hinterlegt wird sie, genau wie die Methode cgetAction(), in der Klasse BookController.

Listing 5

/**
* @RestView
*/
public function getAction($id)
{
  $book = $this->getDoctrine()
    ->getRepository("PhpmagazinRestBundle:Book")
    ->find($id);

  return array('book' => $book);
}

Der Aufruf des URL /books/1 sollte nun den Buchdatensatz mit der ID 1 JSON-kodiert ausgeben.

Datensätze anlegen und aktualisieren

Neue Datensätze werden über einen POST-Request angelegt. Das REST-Paradigma gibt dabei vor, dass der Ziel-URL für den Request die übergeordnete Ebene der Entität (/books) ist. Betrachtet man die Methode als Verb im REST-Kontext, macht dies semantisch Sinn. Ein URL /books/new wird entsprechend nicht benötigt.

Bevor die Action angelegt wird, muss noch eine Formular-Klasse angelegt werden. Diese wird beispielsweise genutzt, um die übergebenen Daten an die Entität zu binden. Auch hier hilft Doctrine wieder, da die Formular-Klasse automatisch generiert werden kann:

$ php app/console generate:doctrine:form PhpmagazinRestBundle:Book

Anschließend gibt es eine neue Klasse BookType im Unterverzeichnis Form. Diese muss noch minimal angepasst werden, da Symfony automatisch CSRF-Absicherung aktiviert. Die macht bei einem REST-API keinen Sinn. Deaktiviert wird sie, indem die Option csrf_protection im Default-Option Array auf false gesetzt wird. Die Methode setDetaultOptions() sollte anschließend wie folgt aussehen:

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
  $resolver->setDefaults(array(
    'data_class' => 'PhpmagazinRestBundleEntityBook',
    'csrf_protection' => false
  )); 
}

Zudem wird der Name angepasst. Der automatisch von Symfony erzeugte kann theoretisch bestehen bleiben, ist aber relativ lang. Da er in den Requests angegeben wird, ist es übersichtlicher, ihn kurz zu halten. Er wird abgekürzt auf book, indem der Rückgabewert der Methode getName() in der BookType-Klasse angepasst wird. Hier nun die fertige Action zum Erzeugen neuer Datensätze (Listing 6).

Listing 6

/**
* @RestView
*/
public function cpostAction( )
{
  $book = new PhpmagazinRestBundleEntityBook();
  $form = $this->createForm( new PhpmagazinRestBundleFormBookType(), $book );
  $form->bind( $this->getRequest() );
  if ( $form->isValid() ) {
    $em = $this->getDoctrine()->getManager();
    $em->persist( $book );
    $em->flush();

    return $this->redirectView(
      $this->generateUrl(
        'get_book',
        array( 'id' => $book->getId() )
      ),
      FOSRestUtilCodes::HTTP_CREATED
    );
  }

  return array(
    'processedForm' => $form
  );
}

Ähnlich ist das Vorgehen beim Aktualisieren von Datensätzen mittels PUT-Request. Hier wird das Buch vorab anhand der im URL enthaltenen ID geladen. Der Endpunkt ist nun nicht mehr /books, sondern /books/ (Listing 7).

Listing 7

/**
* @RestView
*/
public function putAction($id)
{
  $em = $this->getDoctrine()->getManager();
  $book = $em->getRepository('PhpmagazinRestBundle:Book')->find($id);

  $form = $this->createForm(new PhpmagazinRestBundleFormBookType(), $book);
  $form->bind( $this->getRequest() );

  if ($form->isValid()) {
    $em->persist($book);
    $em->flush();

    return $this->redirectView(
      $this->generateUrl(
        'get_book',
        array( 'id' => $book->getId()
      ),
      FOSRestUtilCodes::HTTP_NO_CONTENT
    );
  }

  return array(
    'form' => $form,
  );
}

Um im Livebetrieb Benutzereingaben detailliert zu prüfen, müssen zudem Filter zur Validierung der Entitäten angelegt werden. Mit ihnen kann das erwartete Format der Eingaben exakt definiert werden. Ich empfehle an der Stelle das Kapitel Validation in der offiziellen Symfony-Dokumentation. Dort wird das notwendige Vorgehen erläutert.

Datensätze löschen

Um alle CRUD-Aktionen zu unterstützten, fehlt noch eine Funktion zum Löschen von Büchern. Auch hier ist der Endpunkt /books/. Auch hier wird die Aktion durch die HTTP-Methode signalisiert. Um Datensätze zu löschen, werden DELETE-Requests verwendet. Listing 8 zeigt eine Action, die den im URL angegebenen Datensatz entfernt.

Listing 8

/**
* @RestView
*/
public function deleteAction($id)
{
  $em = $this->getDoctrine()->getManager();
  $book = $em->getRepository('PhpmagazinRestBundle:Book')->find($id);

  $em->remove($book);
  $em->flush();

  return $this->view(null, FOSRestUtilCodes::HTTP_NO_CONTENT);
}

Damit sind alle Operationen grundlegend realisiert. Sie können nun bereits verwendet werden, um eine Buchsammlung zu verwalten. Für den Livebetrieb fehlt jedoch noch das eine oder andere Extra, das hier nicht berücksichtigt werden konnte.

Authentifizierung und Authentisierung

Die Authentifizierung muss über Symfony eingerichtet werden. Das RestBundle ist daran nicht beteiligt. Symfony unterstützt verschiedene Authentifizierungs-Mechanismen. Bei geänderten Anforderungen können diese auch problemlos ausgetauscht werden.

Die Standard HTTP-Authentifizierung ist für die Entwicklungsumgebung ausreichend, für den Livebetrieb jedoch zu unsicher. Das Passwort wird nur unzureichend geschützt und Replay-Attacken nicht verhindert. Eine gute Alternative ist die ebenfalls unterstützte WSSE-Authentifizierung. Wer sein API einer breiten Öffentlichkeit zugänglich macht, kann zudem auch mit OAuth arbeiten. Dies ist jedoch ungleich komplizierter und hier kein Thema.

In einer realen Applikation muss zudem noch die Authentisierung berücksichtigt werden. Ein Benutzer darf nur seine eigenen Datensätze einsehen und verändern. Dies gilt es sicherzustellen. Weitere Informationen rund um die Themen Authentifizierung und Authentisierung bietet die offizielle Symfony-Dokumentation.

Fazit

Das FOSRestBundle ist bei der Implementierung eines RESTful Web Service für eine Symfony-Anwendung eine große Hilfe. Dem Entwickler werden viele sich wiederholende Detailarbeiten abgenommen. Dies spart Aufwand und Zeit und ermöglicht zeitnah Ergebnisse. Die gezeigte Anwendung ist in ihrer Funktion auf das Nötigste reduziert, um den Rahmen nicht zu sprengen. Sinnvolle Erweiterungen sind, wie bereits erwähnt, die Konfiguration der Authentifizierung sowie die Berücksichtigung der Authentisierung. Ebenfalls sehr zu empfehlen sind Funktionstests für die REST-Actions, um korrektes Verhalten nachhaltig sicherzustellen. Dies macht Symfony dank mitgelieferter Testsuite ebenfalls sehr einfach möglich. Wird das API öffentlich freigegeben, müssen zudem eventuell weitere Repräsentationen unterstützt werden. Sinnvoll wäre unter anderem HTML. Durch die Möglichkeit, als Entwickler über Hyperlinks durch die Schnittstelle zu navigieren, wird es für Entwickler deutlich einfacher mit dem API zu arbeiten. Dadurch würde sie auch alle Level-3-Anforderungen des anfangs erwähnten Richardson Maturity Models erfüllen.

Wer zum Beispiel Apps entwickelt und diese an eine Symfony-Applikation anbinden möchte, muss eine Schnittstelle bereitstellen. Vieles spricht dafür, ein REST-API zu implementieren. Dieses Tutorial zeigt, wieso.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -