Whatsapp Telegram Telegram Call Anrufen

API-Entwicklung in Symfony


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:

  1. Erstellen von RESTful APIs
  2. Verarbeitung von JSON-Anfragen und -Antworten
  3. Verwendung der Serializer-Komponente
  4. API-Authentifizierung und JWT-Tokens
  5. 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 bei JsonResponse 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 und JsonResponse, 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:

Eine API mit API Platform bereitstellen

The Serializer Component

Security

NelmioApiDocBundle

lexik/jwt-authentication-bundle


CEO Image

Ali Ajjoub

info@ajjoub.com

Adresse 0049-15773651670

Adresse Jacob-winter-platz,1 01239 Dresden

Buchen Sie jetzt Ihren Termin für eine umfassende und individuelle Beratung.

Termin Buchen

Kontaktieren Sie uns

Lassen Sie uns K o n t a k t aufnehmen!