Die Entwicklung von APIs (Application Programming Interfaces) ist ein zentraler Bestandteil moderner Webanwendungen. Symfony bietet leistungsstarke Tools und Komponenten, um RESTful APIs effizient zu erstellen und zu verwalten. In diesem Artikel werden wir uns ausführlich mit der API-Entwicklung in Symfony beschäftigen und die folgenden Themen behandeln:
- Erstellen von RESTful APIs
- Verarbeitung von JSON-Anfragen und -Antworten
- Verwendung der Serializer-Komponente
- API-Authentifizierung und JWT-Tokens
- Versionierung und Dokumentation von APIs (OpenAPI/Swagger)
1. Erstellen von RESTful APIs
1.1 Was ist eine RESTful API?
Eine RESTful API (Representational State Transfer) ist ein Architekturstil für verteilte Hypermedia-Systeme. Sie nutzt HTTP-Methoden wie GET, POST, PUT und DELETE, um Ressourcen zu erstellen, zu lesen, zu aktualisieren und zu löschen.
1.2 Grundlagen der API-Entwicklung in Symfony
Symfony ermöglicht die Erstellung von APIs durch die Definition von Routen und Controllern, die HTTP-Anfragen verarbeiten und entsprechende Antworten liefern.
1.3 Erstellen einer einfachen API-Ressource
Beispiel: Verwaltung von "Book"-Ressourcen
Angenommen, wir möchten eine API erstellen, um Bücher zu verwalten.
Schritt 1: Erstellen der Entität
// src/Entity/Book.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Book
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private string $title;
#[ORM\Column(length: 255)]
private string $author;
#[ORM\Column(type: 'text')]
private string $description;
// Getter und Setter ...
}
Schritt 2: Erstellen des Controllers
// src/Controller/Api/BookController.php
namespace App\Controller\Api;
use App\Entity\Book;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/api/books', name: 'api_books_')]
class BookController
{
#[Route('', name: 'list', methods: ['GET'])]
public function list(EntityManagerInterface $em): JsonResponse
{
$books = $em->getRepository(Book::class)->findAll();
// Manuelles Serialisieren (nicht empfohlen, später verwenden wir den Serializer)
$data = [];
foreach ($books as $book) {
$data[] = [
'id' => $book->getId(),
'title' => $book->getTitle(),
'author' => $book->getAuthor(),
'description' => $book->getDescription(),
];
}
return new JsonResponse($data);
}
#[Route('/{id}', name: 'show', methods: ['GET'])]
public function show(Book $book): JsonResponse
{
$data = [
'id' => $book->getId(),
'title' => $book->getTitle(),
'author' => $book->getAuthor(),
'description' => $book->getDescription(),
];
return new JsonResponse($data);
}
// Weitere Methoden für POST, PUT, DELETE ...
}
Schritt 3: Routen überprüfen
Mit den obigen Routen können wir:
- GET /api/books: Alle Bücher abrufen
- GET /api/books/{id}: Ein einzelnes Buch abrufen
1.4 Verwendung von HTTP-Methoden
- GET: Lesen von Ressourcen
- POST: Erstellen neuer Ressourcen
- PUT/PATCH: Aktualisieren von Ressourcen
- DELETE: Löschen von Ressourcen
1.5 Routen mit Methoden verknüpfen
Durch Angabe der methods
-Option in der #[Route]
-Annotation können wir festlegen, welche HTTP-Methoden erlaubt sind.
Beispiel:
#[Route('', name: 'create', methods: ['POST'])]
public function create(Request $request, EntityManagerInterface $em): JsonResponse
{
// Neue Ressource erstellen
}
2. Verarbeitung von JSON-Anfragen und -Antworten
2.1 Empfangen von JSON-Daten
Um JSON-Daten aus einer Anfrage zu lesen, verwenden wir das Request
-Objekt.
Beispiel:
use Symfony\Component\HttpFoundation\Request;
public function create(Request $request, EntityManagerInterface $em): JsonResponse
{
$data = json_decode($request->getContent(), true);
$book = new Book();
$book->setTitle($data['title']);
$book->setAuthor($data['author']);
$book->setDescription($data['description']);
$em->persist($book);
$em->flush();
return new JsonResponse(['status' => 'Buch erstellt'], JsonResponse::HTTP_CREATED);
}
2.2 Senden von JSON-Antworten
Wir verwenden JsonResponse
, um JSON-Daten zurückzugeben.
Beispiel:
return new JsonResponse($data, JsonResponse::HTTP_OK);
2.3 Fehlerbehandlung und Statuscodes
Es ist wichtig, angemessene HTTP-Statuscodes und Fehlermeldungen zurückzugeben.
Beispiel bei einer ungültigen Anfrage:
if (!$data['title'] || !$data['author']) {
return new JsonResponse(['error' => 'Ungültige Daten'], JsonResponse::HTTP_BAD_REQUEST);
}
2.4 Validierung von Daten
Wir können die Symfony-Validator-Komponente verwenden, um eingehende Daten zu validieren.
Beispiel:
use Symfony\Component\Validator\Validator\ValidatorInterface;
public function create(Request $request, EntityManagerInterface $em, ValidatorInterface $validator): JsonResponse
{
$data = json_decode($request->getContent(), true);
$book = new Book();
$book->setTitle($data['title']);
$book->setAuthor($data['author']);
$book->setDescription($data['description']);
$errors = $validator->validate($book);
if (count($errors) > 0) {
$errorsString = (string) $errors;
return new JsonResponse(['error' => $errorsString], JsonResponse::HTTP_BAD_REQUEST);
}
$em->persist($book);
$em->flush();
return new JsonResponse(['status' => 'Buch erstellt'], JsonResponse::HTTP_CREATED);
}
3. Verwendung der Serializer-Komponente
3.1 Einführung in die Serializer-Komponente
Die Serializer-Komponente von Symfony ermöglicht das Umwandeln von Objekten in verschiedene Formate wie JSON oder XML und umgekehrt.
3.2 Installation
Falls nicht bereits installiert:
composer require symfony/serializer-pack
3.3 Serialisieren von Objekten
Beispiel:
use Symfony\Component\Serializer\SerializerInterface;
public function list(SerializerInterface $serializer, EntityManagerInterface $em): JsonResponse
{
$books = $em->getRepository(Book::class)->findAll();
$data = $serializer->serialize($books, 'json');
return new JsonResponse($data, JsonResponse::HTTP_OK, [], true);
}
Erklärung:
- $serializer->serialize(): Wandelt das Objekt oder Array in JSON um.
- Der vierte Parameter
true
beiJsonResponse
gibt an, dass die Daten bereits JSON-kodiert sind.
3.4 Anpassen der Serialisierung mit Normalizern
Manchmal möchten wir kontrollieren, welche Felder serialisiert werden.
Verwendung von Serializer-Gruppen
Schritt 1: Definieren von Gruppen in der Entität
use Symfony\Component\Serializer\Annotation\Groups;
class Book
{
#[Groups(['list', 'detail'])]
private ?int $id = null;
#[Groups(['list', 'detail'])]
private string $title;
#[Groups(['detail'])]
private string $description;
// ...
}
Schritt 2: Angeben der Gruppen beim Serialisieren
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
public function list(SerializerInterface $serializer, EntityManagerInterface $em): JsonResponse
{
$books = $em->getRepository(Book::class)->findAll();
$data = $serializer->serialize($books, 'json', [AbstractNormalizer::GROUPS => ['list']]);
return new JsonResponse($data, JsonResponse::HTTP_OK, [], true);
}
3.5 Deserialisieren von Daten
Der Serializer kann auch JSON-Daten in Objekte umwandeln.
Beispiel:
public function create(Request $request, SerializerInterface $serializer, EntityManagerInterface $em, ValidatorInterface $validator): JsonResponse
{
$data = $request->getContent();
$book = $serializer->deserialize($data, Book::class, 'json');
$errors = $validator->validate($book);
if (count($errors) > 0) {
$errorsString = (string) $errors;
return new JsonResponse(['error' => $errorsString], JsonResponse::HTTP_BAD_REQUEST);
}
$em->persist($book);
$em->flush();
return new JsonResponse(['status' => 'Buch erstellt'], JsonResponse::HTTP_CREATED);
}
4. API-Authentifizierung und JWT-Tokens
4.1 Warum Authentifizierung für APIs?
APIs müssen oft geschützt werden, um sicherzustellen, dass nur autorisierte Benutzer oder Anwendungen darauf zugreifen können.
4.2 JSON Web Tokens (JWT)
JWT ist ein offener Standard für die sichere Übertragung von Informationen als JSON-Objekt. Es wird häufig für die Authentifizierung und Autorisierung in APIs verwendet.
4.3 Installation von JWT-Authentifizierung
Wir verwenden das LexikJWTAuthenticationBundle.
Schritt 1: Installation
composer require "lexik/jwt-authentication-bundle"
Schritt 2: Generieren der SSL-Schlüssel
mkdir -p config/jwt
openssl genrsa -out config/jwt/private.pem -aes256 4096
openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem
Während der Generierung werden Sie nach einer Passphrase gefragt.
Schritt 3: Konfiguration
In .env
hinzufügen:
###> lexik/jwt-authentication-bundle ###
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=IhrePassphrase
###< lexik/jwt-authentication-bundle ###
In config/packages/lexik_jwt_authentication.yaml
:
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 3600
Schritt 4: Sicherheitskonfiguration
In config/packages/security.yaml
:
security:
# ...
firewalls:
login:
pattern: ^/api/login
stateless: true
json_login:
check_path: /api/login
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
jwt: ~
access_control:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
Schritt 5: Erstellen des Login-Controllers
// src/Controller/Api/AuthController.php
namespace App\Controller\Api;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/api', name: 'api_auth_')]
class AuthController
{
#[Route('/login', name: 'login', methods: ['POST'])]
public function login()
{
// Die Logik wird vom Security-System übernommen
throw new \Exception('This method can be blank - it will be intercepted by the security layer.');
}
}
4.4 Verwendung des Tokens
Nach erfolgreicher Anmeldung erhält der Benutzer ein JWT-Token, das er bei nachfolgenden Anfragen im Authorization
-Header senden muss.
Beispiel:
GET /api/books HTTP/1.1
Host: example.com
Authorization: Bearer <Ihr-JWT-Token>
4.5 Schutz der API-Routen
Durch die Sicherheitskonfiguration werden alle /api
-Routen geschützt, und nur authentifizierte Benutzer mit gültigem Token können darauf zugreifen.
5. Versionierung und Dokumentation von APIs (OpenAPI/Swagger)
5.1 Warum API-Versionierung?
API-Versionierung ermöglicht es, Änderungen an der API vorzunehmen, ohne bestehende Clients zu stören. Neue Funktionen können hinzugefügt werden, während alte Funktionen weiterhin unterstützt werden.
5.2 Strategien für die Versionierung
- URI-basierte Versionierung: Hinzufügen der Versionsnummer in der URL, z. B.
/api/v1/books
. - Header-basierte Versionierung: Verwendung eines speziellen HTTP-Headers, z. B.
Accept: application/vnd.example.v1+json
.
5.3 Implementierung der Versionierung in Symfony
Beispiel: URI-basierte Versionierung
// src/Controller/Api/v1/BookController.php
namespace App\Controller\Api\v1;
// ... (gleicher Code wie zuvor)
Anpassung der Route:
#[Route('/api/v1/books', name: 'api_v1_books_')]
5.4 Dokumentation mit OpenAPI/Swagger
Installation von NelmioApiDocBundle
Dieses Bundle ermöglicht die Erstellung einer API-Dokumentation basierend auf OpenAPI/Swagger.
composer require nelmio/api-doc-bundle
Konfiguration
In config/packages/nelmio_api_doc.yaml
:
nelmio_api_doc:
documentation:
info:
title: 'Meine API'
description: 'API Dokumentation'
version: '1.0.0'
Anmerkungen hinzufügen
Verwenden Sie Annotations oder PHP-Attribute, um Ihre API zu dokumentieren.
Beispiel mit PHP-Attributen:
use OpenApi\Annotations as OA;
#[Route('/api/v1/books', name: 'api_v1_books_')]
class BookController
{
/**
* @OA\Get(
* path="/api/v1/books",
* summary="Liste aller Bücher",
* @OA\Response(
* response=200,
* description="Erfolgreiche Operation",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Book"))
* )
* )
*/
public function list(SerializerInterface $serializer, EntityManagerInterface $em): JsonResponse
{
// ...
}
}
Zugriff auf die Dokumentation
Nach der Konfiguration können Sie die Dokumentation unter /api/doc
aufrufen.
5.5 Vorteile von OpenAPI/Swagger
- Automatische Dokumentation: Erleichtert die Erstellung und Pflege der API-Dokumentation.
- Interaktives Interface: Ermöglicht das Testen der API direkt im Browser.
- Konsistenz: Stellt sicher, dass die Dokumentation mit dem tatsächlichen API-Code übereinstimmt.
Zusammenfassung
- Erstellen von RESTful APIs: Verwenden Sie die HTTP-Methoden und Routen, um Ressourcen zu verwalten.
- Verarbeitung von JSON: Nutzen Sie das
Request
-Objekt undJsonResponse
, um JSON-Daten zu empfangen und zu senden. - Serializer-Komponente: Erleichtert die Umwandlung von Objekten in JSON und umgekehrt, mit Unterstützung für Normalisierung und Gruppen.
- API-Authentifizierung: Implementieren Sie JWT-Tokens, um Ihre API zu schützen und autorisierten Zugriff zu ermöglichen.
- Versionierung und Dokumentation: Verwenden Sie Strategien zur Versionierung und nutzen Sie OpenAPI/Swagger, um Ihre API zu dokumentieren.
Weiterführende Ressourcen
Symfony Dokumentation: