Files
michaelschiemer/docs/guides/controllers.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

18 KiB

Controller-Anleitung

Diese Anleitung erklärt, wie Controller im Framework funktionieren und wie Sie sie effektiv nutzen können.

Was sind Controller?

Controller sind Klassen, die für die Verarbeitung von HTTP-Anfragen verantwortlich sind. Sie nehmen Anfragen entgegen, führen die erforderliche Geschäftslogik aus und geben Antworten zurück. Controller dienen als Vermittler zwischen den Modellen (Daten und Geschäftslogik) und den Views (Präsentationsschicht).

Grundlegende Controller

Erstellen eines Controllers

Controller werden im Verzeichnis src/Application/Controllers gespeichert. Ein einfacher Controller sieht wie folgt aus:

<?php

namespace App\Application\Controllers;

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

class UserController extends Controller
{
    public function index(Request $request): Response
    {
        return $this->view('users.index', [
            'users' => User::all()
        ]);
    }
    
    public function show(Request $request, int $id): Response
    {
        $user = User::find($id);
        
        if (!$user) {
            return $this->notFound('Benutzer nicht gefunden');
        }
        
        return $this->view('users.show', [
            'user' => $user
        ]);
    }
}

Controller-Basisklasse

Alle Controller sollten von der App\Framework\Http\Controller-Klasse erben, die nützliche Methoden für die Erstellung von Antworten bereitstellt:

// Gibt eine View zurück
$this->view('users.index', ['users' => $users]);

// Gibt JSON zurück
$this->json(['name' => 'John', 'email' => 'john@example.com']);

// Leitet zu einer anderen URL um
$this->redirect('/users');

// Leitet zurück zur vorherigen Seite um
$this->back();

// Gibt einen Fehler zurück
$this->error('Ein Fehler ist aufgetreten', 500);

// Gibt einen 404-Fehler zurück
$this->notFound('Seite nicht gefunden');

// Gibt einen 403-Fehler zurück
$this->forbidden('Zugriff verweigert');

// Gibt eine leere Antwort zurück
$this->noContent();

Anfragen verarbeiten

Zugriff auf Anfragedaten

Sie können auf die Daten einer Anfrage über das Request-Objekt zugreifen:

public function store(Request $request): Response
{
    // Zugriff auf Formulardaten
    $name = $request->input('name');
    $email = $request->input('email');
    
    // Zugriff auf Formulardaten mit Standardwert
    $age = $request->input('age', 18);
    
    // Prüfen, ob ein Feld vorhanden ist
    if ($request->has('address')) {
        // ...
    }
    
    // Zugriff auf alle Formulardaten
    $data = $request->all();
    
    // Zugriff auf bestimmte Formulardaten
    $userData = $request->only(['name', 'email']);
    $nonUserData = $request->except(['name', 'email']);
    
    // Zugriff auf URL-Parameter
    $page = $request->query('page', 1);
    
    // Zugriff auf Dateien
    $file = $request->file('avatar');
    
    // Zugriff auf Header
    $token = $request->header('Authorization');
    
    // Zugriff auf Cookies
    $remember = $request->cookie('remember', false);
    
    // Zugriff auf die Anfragemethode
    $method = $request->method();
    
    // Prüfen, ob die Anfrage eine AJAX-Anfrage ist
    if ($request->isAjax()) {
        // ...
    }
    
    // Prüfen, ob die Anfrage eine bestimmte Methode verwendet
    if ($request->isMethod('POST')) {
        // ...
    }
    
    // Zugriff auf die Anfrage-URL
    $url = $request->url();
    
    // Zugriff auf die vollständige Anfrage-URL mit Abfrageparametern
    $fullUrl = $request->fullUrl();
    
    // Zugriff auf den Pfad der Anfrage
    $path = $request->path();
}

Validierung von Anfragedaten

Sie können Anfragedaten direkt im Controller validieren:

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|confirmed',
    ]);
    
    // Die Validierung wurde bestanden, fahren Sie mit der Speicherung fort
    $user = User::create($request->only(['name', 'email', 'password']));
    
    return $this->redirect('/users')->with('success', 'Benutzer erstellt');
}

Wenn die Validierung fehlschlägt, wird automatisch eine Antwort mit den Validierungsfehlern zurückgegeben. Bei einer regulären Anfrage wird der Benutzer zur vorherigen Seite umgeleitet, bei einer AJAX-Anfrage wird eine JSON-Antwort mit den Fehlern zurückgegeben.

Weitere Informationen zur Validierung finden Sie in der Validierungs-Anleitung.

Antworten erstellen

HTML-Antworten

Um eine HTML-Antwort zu erstellen, verwenden Sie die view-Methode:

public function index(): Response
{
    $users = User::all();
    
    return $this->view('users.index', [
        'users' => $users
    ]);
}

Die view-Methode akzeptiert den Namen der View und ein Array mit Daten, die an die View übergeben werden sollen. Der Name der View entspricht dem Pfad der View-Datei relativ zum Verzeichnis resources/views, wobei Punkte als Verzeichnistrennzeichen verwendet werden. Im obigen Beispiel wird die Datei resources/views/users/index.php gerendert.

JSON-Antworten

Um eine JSON-Antwort zu erstellen, verwenden Sie die json-Methode:

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

Die json-Methode akzeptiert ein Array oder ein Objekt, das in JSON konvertiert werden soll, und einen optionalen HTTP-Statuscode:

return $this->json(['error' => 'Nicht gefunden'], 404);

Weiterleitungen

Um eine Weiterleitung zu erstellen, verwenden Sie die redirect-Methode:

public function store(Request $request): Response
{
    // Benutzer erstellen
    
    return $this->redirect('/users');
}

Sie können auch zur vorherigen Seite umleiten:

public function store(Request $request): Response
{
    // Validierungsfehler
    
    return $this->back();
}

Sie können Flash-Daten mit der Weiterleitung senden:

return $this->redirect('/users')
    ->with('success', 'Benutzer erstellt');

Diese Flash-Daten sind in der nächsten Anfrage über die Session verfügbar:

$message = $request->session()->get('success');

Fehlerantworten

Um eine Fehlerantwort zu erstellen, verwenden Sie eine der Fehlermethoden:

// 404 Not Found
return $this->notFound('Benutzer nicht gefunden');

// 403 Forbidden
return $this->forbidden('Sie haben keine Berechtigung, diesen Benutzer zu bearbeiten');

// 401 Unauthorized
return $this->unauthorized('Bitte melden Sie sich an');

// 400 Bad Request
return $this->badRequest('Ungültige Anfrage');

// 500 Internal Server Error
return $this->error('Ein Fehler ist aufgetreten');

Datei-Downloads

Um eine Datei zum Download anzubieten, verwenden Sie die download-Methode:

public function download(int $id): Response
{
    $file = File::find($id);
    
    return $this->download($file->path, $file->name);
}

Datei-Streams

Um eine Datei zu streamen, verwenden Sie die stream-Methode:

public function stream(int $id): Response
{
    $video = Video::find($id);
    
    return $this->stream($video->path, $video->mime_type);
}

Dependency Injection

Das Framework unterstützt Dependency Injection in Controller-Konstruktoren und -Methoden:

class UserController extends Controller
{
    private UserRepository $userRepository;
    
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }
    
    public function index(Request $request, Logger $logger): Response
    {
        $logger->info('Benutzerindex aufgerufen');
        
        $users = $this->userRepository->all();
        
        return $this->view('users.index', [
            'users' => $users
        ]);
    }
}

Middleware

Sie können Middleware auf Controller-Ebene anwenden, indem Sie die middleware-Methode im Konstruktor aufrufen:

class AdminController extends Controller
{
    public function __construct()
    {
        $this->middleware(AuthMiddleware::class);
        $this->middleware(AdminMiddleware::class);
    }
    
    // ...
}

Sie können die Middleware auch auf bestimmte Methoden beschränken:

public function __construct()
{
    $this->middleware(AuthMiddleware::class)->only(['index', 'show']);
    $this->middleware(AdminMiddleware::class)->except(['index', 'show']);
}

Ressourcen-Controller

Ressourcen-Controller bieten eine bequeme Möglichkeit, CRUD-Operationen für eine Ressource zu implementieren. Ein Ressourcen-Controller enthält die folgenden Methoden:

  • index: Zeigt eine Liste aller Ressourcen an
  • create: Zeigt ein Formular zum Erstellen einer neuen Ressource an
  • store: Speichert eine neu erstellte Ressource
  • show: Zeigt eine bestimmte Ressource an
  • edit: Zeigt ein Formular zum Bearbeiten einer Ressource an
  • update: Aktualisiert eine bestimmte Ressource
  • destroy: Löscht eine bestimmte Ressource

Hier ist ein Beispiel für einen Ressourcen-Controller:

class PhotoController extends Controller
{
    public function index(): Response
    {
        $photos = Photo::all();
        
        return $this->view('photos.index', [
            'photos' => $photos
        ]);
    }
    
    public function create(): Response
    {
        return $this->view('photos.create');
    }
    
    public function store(Request $request): Response
    {
        $this->validate($request, [
            'title' => 'required|string|max:255',
            'image' => 'required|image|max:2048',
        ]);
        
        $photo = new Photo();
        $photo->title = $request->input('title');
        $photo->path = $request->file('image')->store('photos');
        $photo->save();
        
        return $this->redirect('/photos')->with('success', 'Foto hochgeladen');
    }
    
    public function show(int $id): Response
    {
        $photo = Photo::find($id);
        
        if (!$photo) {
            return $this->notFound('Foto nicht gefunden');
        }
        
        return $this->view('photos.show', [
            'photo' => $photo
        ]);
    }
    
    public function edit(int $id): Response
    {
        $photo = Photo::find($id);
        
        if (!$photo) {
            return $this->notFound('Foto nicht gefunden');
        }
        
        return $this->view('photos.edit', [
            'photo' => $photo
        ]);
    }
    
    public function update(Request $request, int $id): Response
    {
        $photo = Photo::find($id);
        
        if (!$photo) {
            return $this->notFound('Foto nicht gefunden');
        }
        
        $this->validate($request, [
            'title' => 'required|string|max:255',
        ]);
        
        $photo->title = $request->input('title');
        $photo->save();
        
        return $this->redirect('/photos')->with('success', 'Foto aktualisiert');
    }
    
    public function destroy(int $id): Response
    {
        $photo = Photo::find($id);
        
        if (!$photo) {
            return $this->notFound('Foto nicht gefunden');
        }
        
        $photo->delete();
        
        return $this->redirect('/photos')->with('success', 'Foto gelöscht');
    }
}

Um einen Ressourcen-Controller zu registrieren, verwenden Sie die resource-Methode des Routers:

$router->resource('photos', PhotoController::class);

API-Controller

API-Controller sind ähnlich wie reguläre Controller, geben jedoch in der Regel JSON-Antworten zurück:

class ApiUserController extends Controller
{
    public function index(): Response
    {
        $users = User::all();
        
        return $this->json([
            'data' => $users
        ]);
    }
    
    public function show(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(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'
        ]);
    }
}

Single Action Controller

Wenn ein Controller nur eine einzige Aktion ausführt, können Sie einen Single Action Controller erstellen, der die __invoke-Methode implementiert:

class ShowDashboardController extends Controller
{
    public function __invoke(Request $request): Response
    {
        $stats = [
            'users' => User::count(),
            'posts' => Post::count(),
            'comments' => Comment::count(),
        ];
        
        return $this->view('dashboard', [
            'stats' => $stats
        ]);
    }
}

Um einen Single Action Controller zu registrieren, geben Sie einfach die Controller-Klasse an:

$router->get('/dashboard', ShowDashboardController::class);

Controller-Organisation

Namensräume

Sie können Controller in Namensräumen organisieren, um sie besser zu strukturieren:

namespace App\Application\Controllers\Admin;

class UserController extends Controller
{
    // ...
}

Controller-Gruppen

Sie können Controller in Gruppen organisieren, indem Sie Routengruppen mit einem Namensraum verwenden:

$router->group('/admin', function (Router $router) {
    $router->get('/users', [UserController::class, 'index']);
    $router->get('/posts', [PostController::class, 'index']);
})->namespace('App\\Application\\Controllers\\Admin');

Beste Praktiken

Schlanke Controller

Controller sollten schlank sein und sich auf die Verarbeitung von HTTP-Anfragen konzentrieren. Komplexe Geschäftslogik sollte in Service-Klassen oder Modelle ausgelagert werden:

class UserController extends Controller
{
    private UserService $userService;
    
    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }
    
    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 = $this->userService->createUser(
            $request->input('name'),
            $request->input('email'),
            $request->input('password')
        );
        
        return $this->redirect('/users')->with('success', 'Benutzer erstellt');
    }
}

Wiederverwendung von Validierungsregeln

Wenn Sie dieselben Validierungsregeln in mehreren Controllern verwenden, sollten Sie sie in eine separate Klasse auslagern:

class UserValidationRules
{
    public static function forCreation(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users,email',
            'password' => 'required|string|min:8',
        ];
    }
    
    public static function forUpdate(int $userId): array
    {
        return [
            'name' => 'string|max:255',
            'email' => 'email|unique:users,email,' . $userId,
        ];
    }
}

Dann können Sie diese Regeln in Ihren Controllern verwenden:

public function store(Request $request): Response
{
    $this->validate($request, UserValidationRules::forCreation());
    
    // ...
}

public function update(Request $request, int $id): Response
{
    $this->validate($request, UserValidationRules::forUpdate($id));
    
    // ...
}

Verwendung von Form Requests

Für komplexe Validierungslogik können Sie Form Request-Klassen erstellen:

class CreateUserRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users,email',
            'password' => 'required|string|min:8',
        ];
    }
    
    public function messages(): array
    {
        return [
            'name.required' => 'Der Name ist erforderlich',
            'email.unique' => 'Diese E-Mail-Adresse wird bereits verwendet',
        ];
    }
    
    public function authorize(): bool
    {
        return $this->user()->hasPermission('create-users');
    }
}

Dann können Sie die Form Request-Klasse in Ihrem Controller verwenden:

public function store(CreateUserRequest $request): Response
{
    // Die Validierung wurde bereits durchgeführt
    
    $user = User::create($request->validated());
    
    return $this->redirect('/users')->with('success', 'Benutzer erstellt');
}

Weitere Informationen