MVC Architektur Erklärt
MVC Architektur verstehen
MVC trennt Anwendungslogik in drei Komponenten für bessere Wartbarkeit. Lernen Sie Model, View und Controller mit praktischen Beispielen.
Die drei Komponenten
┌─────────────────────────────────────────────────────────────┐ │ MVC PATTERN │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ │ │ ┌────────►│ VIEW │◄───────┐ │ │ │ │ (Template) │ │ │ │ │ └──────────────┘ │ │ │ │ │ │ │ Updates Reads Data │ │ │ │ │ │ │ │ │ │ ┌──────┴───────┐ ┌────────┴──────┐ │ │ │ MODEL │◄──────────────│ CONTROLLER │ │ │ │ (Daten) │ Updates │ (Logik) │ │ │ └──────────────┘ └───────┬───────┘ │ │ │ │ │ User Input │ │ │ │ │ ┌─────┴─────┐ │ │ │ USER │ │ │ └───────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
| Komponente | Verantwortung | Beispiele |
|---|---|---|
| Model | Daten und Geschäftslogik | User, Order, Product |
| View | Darstellung/UI | HTML Templates, JSON Output |
| Controller | Eingabe-Verarbeitung, Koordination | Request Handler, Route Handler |
Einfaches PHP MVC Beispiel
// ========== MODEL ==========
// models/User.php
class User {
public function __construct(
public ?int $id = null,
public string $name = '',
public string $email = '',
public string $createdAt = ''
) {}
}
class UserRepository {
public function __construct(private PDO $db) {}
public function findAll(): array {
$stmt = $this->db->query('SELECT * FROM users ORDER BY created_at DESC');
return array_map(
fn($row) => new User($row['id'], $row['name'], $row['email'], $row['created_at']),
$stmt->fetchAll(PDO::FETCH_ASSOC)
);
}
public function find(int $id): ?User {
$stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? new User($row['id'], $row['name'], $row['email'], $row['created_at']) : null;
}
public function save(User $user): void {
if ($user->id === null) {
$stmt = $this->db->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute([$user->name, $user->email]);
$user->id = (int) $this->db->lastInsertId();
} else {
$stmt = $this->db->prepare('UPDATE users SET name = ?, email = ? WHERE id = ?');
$stmt->execute([$user->name, $user->email, $user->id]);
}
}
public function delete(int $id): void {
$stmt = $this->db->prepare('DELETE FROM users WHERE id = ?');
$stmt->execute([$id]);
}
}
// ========== VIEW ==========
// views/users/index.php
<!DOCTYPE html>
<html>
<head>
<title>Benutzer</title>
</head>
<body>
<h1>Benutzerliste</h1>
<a href="/users/create">Neuer Benutzer</a>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?= htmlspecialchars($user->id) ?></td>
<td><?= htmlspecialchars($user->name) ?></td>
<td><?= htmlspecialchars($user->email) ?></td>
<td>
<a href="/users/<?= $user->id ?>/edit">Bearbeiten</a>
<form action="/users/<?= $user->id ?>" method="POST" style="display:inline">
<input type="hidden" name="_method" value="DELETE">
<button type="submit">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
// ========== CONTROLLER ==========
// controllers/UserController.php
class UserController {
public function __construct(
private UserRepository $userRepository,
private View $view
) {}
// GET /users
public function index(): string {
$users = $this->userRepository->findAll();
return $this->view->render('users/index', [
'users' => $users
]);
}
// GET /users/{id}
public function show(int $id): string {
$user = $this->userRepository->find($id);
if (!$user) {
throw new NotFoundException("User not found");
}
return $this->view->render('users/show', [
'user' => $user
]);
}
// GET /users/create
public function create(): string {
return $this->view->render('users/create', [
'user' => new User()
]);
}
// POST /users
public function store(array $data): void {
$user = new User(
name: $data['name'],
email: $data['email']
);
$this->userRepository->save($user);
header('Location: /users');
exit;
}
// GET /users/{id}/edit
public function edit(int $id): string {
$user = $this->userRepository->find($id);
if (!$user) {
throw new NotFoundException("User not found");
}
return $this->view->render('users/edit', [
'user' => $user
]);
}
// PUT /users/{id}
public function update(int $id, array $data): void {
$user = $this->userRepository->find($id);
if (!$user) {
throw new NotFoundException("User not found");
}
$user->name = $data['name'];
$user->email = $data['email'];
$this->userRepository->save($user);
header('Location: /users');
exit;
}
// DELETE /users/{id}
public function destroy(int $id): void {
$this->userRepository->delete($id);
header('Location: /users');
exit;
}
}
Router und Front Controller
// index.php - Front Controller
require 'vendor/autoload.php';
$router = new Router();
// Routes definieren
$router->get('/users', [UserController::class, 'index']);
$router->get('/users/create', [UserController::class, 'create']);
$router->get('/users/{id}', [UserController::class, 'show']);
$router->post('/users', [UserController::class, 'store']);
$router->get('/users/{id}/edit', [UserController::class, 'edit']);
$router->put('/users/{id}', [UserController::class, 'update']);
$router->delete('/users/{id}', [UserController::class, 'destroy']);
// Request verarbeiten
try {
$response = $router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
echo $response;
} catch (NotFoundException $e) {
http_response_code(404);
echo "Nicht gefunden";
}
MVC Varianten
// MVP (Model-View-Presenter)
// View ist passiver, Presenter hat mehr Kontrolle
// MVVM (Model-View-ViewModel)
// Zwei-Wege-Datenbindung (Angular, Vue)
// MVC in REST APIs
class ApiUserController {
// Gibt JSON statt HTML zurück
public function index(): Response {
$users = $this->userRepository->findAll();
return new JsonResponse([
'data' => array_map(fn($u) => $u->toArray(), $users)
]);
}
public function show(int $id): Response {
$user = $this->userRepository->find($id);
if (!$user) {
return new JsonResponse(['error' => 'Not found'], 404);
}
return new JsonResponse(['data' => $user->toArray()]);
}
}
JavaScript MVC (Vanilla)
// Model
class TodoModel {
constructor() {
this.todos = [];
this.listeners = [];
}
addTodo(text) {
this.todos.push({ id: Date.now(), text, completed: false });
this.notify();
}
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
this.notify();
}
}
deleteTodo(id) {
this.todos = this.todos.filter(t => t.id !== id);
this.notify();
}
subscribe(listener) {
this.listeners.push(listener);
}
notify() {
this.listeners.forEach(listener => listener(this.todos));
}
}
// View
class TodoView {
constructor(container) {
this.container = container;
}
render(todos) {
this.container.innerHTML = `
-
${todos.map(todo => `
- ${todo.text} `).join('')}
💡 Best Practices:
1. Controller sollten dünn sein - Logik ins Model
2. Views sollten keine Geschäftslogik enthalten
3. Models sollten unabhängig von Views sein
4. Nutzen Sie Services für komplexe Geschäftslogik
5. Validierung kann im Model oder separater Klasse sein
2. Views sollten keine Geschäftslogik enthalten
3. Models sollten unabhängig von Views sein
4. Nutzen Sie Services für komplexe Geschäftslogik
5. Validierung kann im Model oder separater Klasse sein