Files
michaelschiemer/docs/api/index.md
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

21 KiB

API-Dokumentation

Diese Dokumentation bietet einen Überblick über die API des Frameworks und wie Sie sie verwenden können, um RESTful APIs zu erstellen.

Einführung

Das Framework bietet umfassende Unterstützung für die Erstellung von RESTful APIs mit Funktionen wie:

  • Einfache Routendefinition für API-Endpunkte
  • Automatische Konvertierung von Daten in JSON
  • Validierung von Anfragen
  • Authentifizierung und Autorisierung
  • Versionierung
  • Rate Limiting
  • API-Dokumentation

API-Routen definieren

Grundlegende API-Routen

Sie können API-Routen in der Datei config/routes.php definieren:

use App\Framework\Routing\Router;
use App\Application\Controllers\Api\UserController;

return function (Router $router) {
    $router->group('/api', function (Router $router) {
        $router->get('/users', [UserController::class, 'index']);
        $router->get('/users/{id}', [UserController::class, 'show']);
        $router->post('/users', [UserController::class, 'store']);
        $router->put('/users/{id}', [UserController::class, 'update']);
        $router->delete('/users/{id}', [UserController::class, 'destroy']);
    });
};

API-Ressourcen-Routen

Für RESTful APIs können Sie API-Ressourcen-Routen verwenden:

$router->apiResource('users', UserController::class);

Dies erstellt die folgenden Routen:

HTTP-Methode URI Aktion Routenname
GET /users index users.index
POST /users store users.store
GET /users/{user} show users.show
PUT/PATCH /users/{user} update users.update
DELETE /users/{user} destroy users.destroy

Verschachtelte API-Ressourcen

Sie können auch verschachtelte API-Ressourcen definieren:

$router->apiResource('users.posts', UserPostController::class);

Dies erstellt Routen wie:

  • /users/{user}/posts
  • /users/{user}/posts/{post}

API-Versionierung

Sie können verschiedene Versionen Ihrer API unterstützen:

$router->group('/api/v1', function (Router $router) {
    $router->apiResource('users', Api\V1\UserController::class);
});

$router->group('/api/v2', function (Router $router) {
    $router->apiResource('users', Api\V2\UserController::class);
});

API-Controller

API-Controller sind spezielle Controller, die JSON-Antworten zurückgeben:

namespace App\Application\Controllers\Api;

use App\Framework\Http\Controller;
use App\Framework\Http\Request;
use App\Framework\Http\Response;
use App\Application\Models\User;

class UserController extends Controller
{
    public function index(Request $request): Response
    {
        $users = User::all();
        
        return $this->json([
            'data' => $users
        ]);
    }
    
    public function show(Request $request, int $id): Response
    {
        $user = User::find($id);
        
        if (!$user) {
            return $this->json([
                'error' => 'Benutzer nicht gefunden'
            ], 404);
        }
        
        return $this->json([
            'data' => $user
        ]);
    }
    
    public function store(Request $request): Response
    {
        $this->validate($request, [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users,email',
            'password' => 'required|string|min:8',
        ]);
        
        $user = User::create($request->only(['name', 'email', 'password']));
        
        return $this->json([
            'data' => $user,
            'message' => 'Benutzer erstellt'
        ], 201);
    }
    
    public function update(Request $request, int $id): Response
    {
        $user = User::find($id);
        
        if (!$user) {
            return $this->json([
                'error' => 'Benutzer nicht gefunden'
            ], 404);
        }
        
        $this->validate($request, [
            'name' => 'string|max:255',
            'email' => 'email|unique:users,email,' . $id,
        ]);
        
        $user->fill($request->only(['name', 'email']));
        $user->save();
        
        return $this->json([
            'data' => $user,
            'message' => 'Benutzer aktualisiert'
        ]);
    }
    
    public function destroy(Request $request, int $id): Response
    {
        $user = User::find($id);
        
        if (!$user) {
            return $this->json([
                'error' => 'Benutzer nicht gefunden'
            ], 404);
        }
        
        $user->delete();
        
        return $this->json([
            'message' => 'Benutzer gelöscht'
        ]);
    }
}

API-Antworten

Erfolgsantworten

Für Erfolgsantworten können Sie die json-Methode verwenden:

// 200 OK
return $this->json([
    'data' => $users
]);

// 201 Created
return $this->json([
    'data' => $user,
    'message' => 'Benutzer erstellt'
], 201);

// 204 No Content
return $this->noContent();

Fehlerantworten

Für Fehlerantworten können Sie ebenfalls die json-Methode verwenden:

// 400 Bad Request
return $this->json([
    'error' => 'Ungültige Anfrage'
], 400);

// 401 Unauthorized
return $this->json([
    'error' => 'Nicht autorisiert'
], 401);

// 403 Forbidden
return $this->json([
    'error' => 'Zugriff verweigert'
], 403);

// 404 Not Found
return $this->json([
    'error' => 'Benutzer nicht gefunden'
], 404);

// 422 Unprocessable Entity (Validierungsfehler)
return $this->json([
    'error' => 'Validierungsfehler',
    'errors' => $validator->errors()
], 422);

// 500 Internal Server Error
return $this->json([
    'error' => 'Serverfehler'
], 500);

Konsistente Antwortstruktur

Es ist wichtig, eine konsistente Antwortstruktur für Ihre API zu haben:

// Erfolgsantwort
return $this->json([
    'success' => true,
    'data' => $data,
    'message' => $message,
    'meta' => [
        'pagination' => [
            'total' => $total,
            'per_page' => $perPage,
            'current_page' => $currentPage,
            'last_page' => $lastPage,
        ]
    ]
]);

// Fehlerantwort
return $this->json([
    'success' => false,
    'error' => $errorMessage,
    'errors' => $validationErrors,
    'code' => $errorCode
], $statusCode);

API-Ressourcen

API-Ressourcen ermöglichen es Ihnen, Modelle in JSON-Antworten zu transformieren:

namespace App\Application\Resources;

use App\Framework\Http\Resources\JsonResource;
use App\Application\Models\User;

class UserResource extends JsonResource
{
    public function toArray(): array
    {
        /** @var User $this->resource */
        return [
            'id' => $this->resource->id,
            'name' => $this->resource->name,
            'email' => $this->resource->email,
            'created_at' => $this->resource->created_at->format('Y-m-d H:i:s'),
            'updated_at' => $this->resource->updated_at->format('Y-m-d H:i:s'),
        ];
    }
}

Verwenden Sie die Ressource in Ihrem Controller:

use App\Application\Resources\UserResource;

public function show(Request $request, int $id): Response
{
    $user = User::find($id);
    
    if (!$user) {
        return $this->json([
            'error' => 'Benutzer nicht gefunden'
        ], 404);
    }
    
    return $this->json([
        'data' => new UserResource($user)
    ]);
}

public function index(Request $request): Response
{
    $users = User::all();
    
    return $this->json([
        'data' => UserResource::collection($users)
    ]);
}

API-Authentifizierung

Token-basierte Authentifizierung

Das Framework unterstützt Token-basierte Authentifizierung für APIs:

namespace App\Application\Controllers\Api;

use App\Framework\Http\Controller;
use App\Framework\Http\Request;
use App\Framework\Http\Response;
use App\Framework\Auth\Auth;
use App\Framework\Auth\Token;

class AuthController extends Controller
{
    public function login(Request $request): Response
    {
        $this->validate($request, [
            'email' => 'required|email',
            'password' => 'required|string',
        ]);
        
        $credentials = $request->only(['email', 'password']);
        
        if (!Auth::attempt($credentials)) {
            return $this->json([
                'error' => 'Ungültige Anmeldeinformationen'
            ], 401);
        }
        
        $user = Auth::user();
        $token = Token::create($user);
        
        return $this->json([
            'data' => [
                'user' => $user,
                'token' => $token,
                'token_type' => 'Bearer',
            ]
        ]);
    }
    
    public function logout(Request $request): Response
    {
        $token = $request->bearerToken();
        
        if ($token) {
            Token::revoke($token);
        }
        
        return $this->json([
            'message' => 'Erfolgreich abgemeldet'
        ]);
    }
}

Authentifizierung mit dem Token

namespace App\Application\Middleware;

use App\Framework\Http\Middleware;
use App\Framework\Http\Request;
use App\Framework\Http\Response;
use App\Framework\Auth\Token;

class ApiAuthMiddleware implements Middleware
{
    public function handle(Request $request, callable $next): Response
    {
        $token = $request->bearerToken();
        
        if (!$token || !Token::validate($token)) {
            return response()->json([
                'error' => 'Nicht autorisiert'
            ], 401);
        }
        
        $user = Token::getUser($token);
        Auth::setUser($user);
        
        return $next($request);
    }
}

Registrieren Sie die Middleware:

// In der Middleware-Konfiguration
$middleware->group('api', [
    ApiAuthMiddleware::class
]);

// In den Routen
$router->group('/api', function (Router $router) {
    // Öffentliche Routen
    $router->post('/login', [AuthController::class, 'login']);
    
    // Geschützte Routen
    $router->group('', function (Router $router) {
        $router->apiResource('users', UserController::class);
        $router->post('/logout', [AuthController::class, 'logout']);
    })->middleware('api');
});

API-Validierung

Die Validierung für APIs funktioniert ähnlich wie für reguläre Anfragen, gibt jedoch JSON-Antworten zurück:

public function store(Request $request): Response
{
    $validator = new Validator($request->all(), [
        'name' => 'required|string|max:255',
        'email' => 'required|email|unique:users,email',
        'password' => 'required|string|min:8',
    ]);
    
    if ($validator->fails()) {
        return $this->json([
            'error' => 'Validierungsfehler',
            'errors' => $validator->errors()
        ], 422);
    }
    
    $user = User::create($validator->validated());
    
    return $this->json([
        'data' => $user,
        'message' => 'Benutzer erstellt'
    ], 201);
}

Oder mit der validate-Methode:

public function store(Request $request): Response
{
    try {
        $this->validate($request, [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users,email',
            'password' => 'required|string|min:8',
        ]);
    } catch (ValidationException $e) {
        return $this->json([
            'error' => 'Validierungsfehler',
            'errors' => $e->errors()
        ], 422);
    }
    
    $user = User::create($request->only(['name', 'email', 'password']));
    
    return $this->json([
        'data' => $user,
        'message' => 'Benutzer erstellt'
    ], 201);
}

API-Paginierung

Das Framework unterstützt Paginierung für API-Antworten:

public function index(Request $request): Response
{
    $page = $request->query('page', 1);
    $perPage = $request->query('per_page', 15);
    
    $users = User::paginate($perPage, $page);
    
    return $this->json([
        'data' => UserResource::collection($users->items()),
        'meta' => [
            'pagination' => [
                'total' => $users->total(),
                'per_page' => $users->perPage(),
                'current_page' => $users->currentPage(),
                'last_page' => $users->lastPage(),
            ]
        ]
    ]);
}

API-Filterung und Sortierung

Sie können Filterung und Sortierung für Ihre API implementieren:

public function index(Request $request): Response
{
    $query = User::query();
    
    // Filterung
    if ($request->has('name')) {
        $query->where('name', 'like', '%' . $request->query('name') . '%');
    }
    
    if ($request->has('email')) {
        $query->where('email', 'like', '%' . $request->query('email') . '%');
    }
    
    if ($request->has('role')) {
        $query->whereHas('roles', function ($q) use ($request) {
            $q->where('name', $request->query('role'));
        });
    }
    
    // Sortierung
    $sortBy = $request->query('sort_by', 'created_at');
    $sortDirection = $request->query('sort_direction', 'desc');
    
    $allowedSortFields = ['id', 'name', 'email', 'created_at'];
    
    if (in_array($sortBy, $allowedSortFields)) {
        $query->orderBy($sortBy, $sortDirection === 'asc' ? 'asc' : 'desc');
    }
    
    // Paginierung
    $page = $request->query('page', 1);
    $perPage = $request->query('per_page', 15);
    
    $users = $query->paginate($perPage, $page);
    
    return $this->json([
        'data' => UserResource::collection($users->items()),
        'meta' => [
            'pagination' => [
                'total' => $users->total(),
                'per_page' => $users->perPage(),
                'current_page' => $users->currentPage(),
                'last_page' => $users->lastPage(),
            ]
        ]
    ]);
}

API-Rate Limiting

Das Framework bietet Rate Limiting für APIs, um Missbrauch zu verhindern:

namespace App\Application\Middleware;

use App\Framework\Http\Middleware;
use App\Framework\Http\Request;
use App\Framework\Http\Response;
use App\Framework\Cache\Cache;

class RateLimitMiddleware implements Middleware
{
    private int $maxRequests;
    private int $timeWindow;
    
    public function __construct(int $maxRequests = 60, int $timeWindow = 60)
    {
        $this->maxRequests = $maxRequests;
        $this->timeWindow = $timeWindow;
    }
    
    public function handle(Request $request, callable $next): Response
    {
        $key = 'rate_limit:' . $this->getIdentifier($request);
        $requests = Cache::get($key, 0);
        
        if ($requests >= $this->maxRequests) {
            return response()->json([
                'error' => 'Zu viele Anfragen',
                'message' => 'Bitte versuchen Sie es später erneut'
            ], 429);
        }
        
        Cache::put($key, $requests + 1, $this->timeWindow);
        
        $response = $next($request);
        
        $response->headers->set('X-RateLimit-Limit', $this->maxRequests);
        $response->headers->set('X-RateLimit-Remaining', $this->maxRequests - $requests - 1);
        
        return $response;
    }
    
    private function getIdentifier(Request $request): string
    {
        if (Auth::check()) {
            return 'user:' . Auth::id();
        }
        
        return 'ip:' . $request->ip();
    }
}

Registrieren Sie die Middleware:

// In der Middleware-Konfiguration
$middleware->group('api', [
    RateLimitMiddleware::class
]);

// Oder mit Parametern
$middleware->group('api', [
    new RateLimitMiddleware(100, 60) // 100 Anfragen pro Minute
]);

API-Dokumentation

Das Framework unterstützt die automatische Generierung von API-Dokumentation mit OpenAPI/Swagger:

namespace App\Application\Controllers\Api;

use App\Framework\Http\Controller;
use App\Framework\Http\Request;
use App\Framework\Http\Response;
use App\Application\Models\User;

/**
 * @OA\Tag(
 *     name="Users",
 *     description="API-Endpunkte für die Benutzerverwaltung"
 * )
 */
class UserController extends Controller
{
    /**
     * @OA\Get(
     *     path="/api/users",
     *     summary="Liste aller Benutzer abrufen",
     *     tags={"Users"},
     *     @OA\Parameter(
     *         name="page",
     *         in="query",
     *         description="Seitennummer",
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Parameter(
     *         name="per_page",
     *         in="query",
     *         description="Anzahl der Elemente pro Seite",
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="Erfolgreiche Operation",
     *         @OA\JsonContent(
     *             @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/User")),
     *             @OA\Property(property="meta", type="object")
     *         )
     *     )
     * )
     */
    public function index(Request $request): Response
    {
        // ...
    }
    
    /**
     * @OA\Get(
     *     path="/api/users/{id}",
     *     summary="Einen bestimmten Benutzer abrufen",
     *     tags={"Users"},
     *     @OA\Parameter(
     *         name="id",
     *         in="path",
     *         required=true,
     *         description="Benutzer-ID",
     *         @OA\Schema(type="integer")
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="Erfolgreiche Operation",
     *         @OA\JsonContent(
     *             @OA\Property(property="data", ref="#/components/schemas/User")
     *         )
     *     ),
     *     @OA\Response(
     *         response=404,
     *         description="Benutzer nicht gefunden",
     *         @OA\JsonContent(
     *             @OA\Property(property="error", type="string")
     *         )
     *     )
     * )
     */
    public function show(Request $request, int $id): Response
    {
        // ...
    }
    
    // Weitere Methoden mit Dokumentation...
}

/**
 * @OA\Schema(
 *     schema="User",
 *     required={"id", "name", "email"},
 *     @OA\Property(property="id", type="integer", format="int64"),
 *     @OA\Property(property="name", type="string"),
 *     @OA\Property(property="email", type="string", format="email"),
 *     @OA\Property(property="created_at", type="string", format="date-time"),
 *     @OA\Property(property="updated_at", type="string", format="date-time")
 * )
 */

Generieren Sie die API-Dokumentation:

php console.php api:docs

Dies erstellt eine OpenAPI/Swagger-Dokumentation, die Sie in einem Browser anzeigen können.

Beste Praktiken für APIs

Versionierung

Versionieren Sie Ihre API, um Änderungen zu ermöglichen, ohne bestehende Clients zu beeinträchtigen:

$router->group('/api/v1', function (Router $router) {
    // API v1 Routen
});

$router->group('/api/v2', function (Router $router) {
    // API v2 Routen
});

Konsistente Benennung

Verwenden Sie konsistente Benennungskonventionen für Ihre API-Endpunkte:

  • Verwenden Sie Substantive im Plural für Ressourcen (z.B. /users statt /user)
  • Verwenden Sie Kebab-Case für URLs (z.B. /user-profiles statt /userProfiles)
  • Verwenden Sie Camel-Case für JSON-Eigenschaften (z.B. firstName statt first_name)

HTTP-Statuscodes

Verwenden Sie die richtigen HTTP-Statuscodes:

  • 200 OK: Erfolgreiche Anfrage
  • 201 Created: Ressource erfolgreich erstellt
  • 204 No Content: Erfolgreiche Anfrage ohne Inhalt
  • 400 Bad Request: Ungültige Anfrage
  • 401 Unauthorized: Authentifizierung erforderlich
  • 403 Forbidden: Keine Berechtigung
  • 404 Not Found: Ressource nicht gefunden
  • 422 Unprocessable Entity: Validierungsfehler
  • 429 Too Many Requests: Rate Limit überschritten
  • 500 Internal Server Error: Serverfehler

Fehlerbehandlung

Implementieren Sie eine konsistente Fehlerbehandlung:

try {
    // Code, der eine Exception werfen könnte
} catch (ValidationException $e) {
    return $this->json([
        'error' => 'Validierungsfehler',
        'errors' => $e->errors()
    ], 422);
} catch (AuthorizationException $e) {
    return $this->json([
        'error' => 'Nicht autorisiert',
        'message' => $e->getMessage()
    ], 403);
} catch (NotFoundException $e) {
    return $this->json([
        'error' => 'Nicht gefunden',
        'message' => $e->getMessage()
    ], 404);
} catch (\Exception $e) {
    // Protokollieren Sie den Fehler
    $this->logger->error('API-Fehler', [
        'exception' => $e->getMessage(),
        'trace' => $e->getTraceAsString()
    ]);
    
    return $this->json([
        'error' => 'Serverfehler',
        'message' => 'Ein unerwarteter Fehler ist aufgetreten'
    ], 500);
}

Dokumentation

Dokumentieren Sie Ihre API gründlich:

  • Beschreiben Sie jeden Endpunkt
  • Dokumentieren Sie alle Parameter
  • Geben Sie Beispiele für Anfragen und Antworten
  • Erklären Sie Fehlerszenarien

Weitere Informationen