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

728 lines
18 KiB
Markdown

# 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
<?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:
```php
// 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:
```php
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:
```php
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](validation.md).
## Antworten erstellen
### HTML-Antworten
Um eine HTML-Antwort zu erstellen, verwenden Sie die `view`-Methode:
```php
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:
```php
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:
```php
return $this->json(['error' => 'Nicht gefunden'], 404);
```
### Weiterleitungen
Um eine Weiterleitung zu erstellen, verwenden Sie die `redirect`-Methode:
```php
public function store(Request $request): Response
{
// Benutzer erstellen
return $this->redirect('/users');
}
```
Sie können auch zur vorherigen Seite umleiten:
```php
public function store(Request $request): Response
{
// Validierungsfehler
return $this->back();
}
```
Sie können Flash-Daten mit der Weiterleitung senden:
```php
return $this->redirect('/users')
->with('success', 'Benutzer erstellt');
```
Diese Flash-Daten sind in der nächsten Anfrage über die Session verfügbar:
```php
$message = $request->session()->get('success');
```
### Fehlerantworten
Um eine Fehlerantwort zu erstellen, verwenden Sie eine der Fehlermethoden:
```php
// 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:
```php
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:
```php
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:
```php
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:
```php
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:
```php
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:
```php
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:
```php
$router->resource('photos', PhotoController::class);
```
## API-Controller
API-Controller sind ähnlich wie reguläre Controller, geben jedoch in der Regel JSON-Antworten zurück:
```php
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:
```php
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:
```php
$router->get('/dashboard', ShowDashboardController::class);
```
## Controller-Organisation
### Namensräume
Sie können Controller in Namensräumen organisieren, um sie besser zu strukturieren:
```php
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:
```php
$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:
```php
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:
```php
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:
```php
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:
```php
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:
```php
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
- [Routing-Anleitung](routing.md): Erfahren Sie mehr über das Routing-System und wie es mit Controllern interagiert.
- [Validierungs-Anleitung](validation.md): Erfahren Sie mehr über die Validierung von Benutzereingaben.
- [Middleware-Anleitung](middleware.md): Erfahren Sie mehr über Middleware und wie sie mit Controllern interagiert.
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.