Appearance
Kapitel 15: Vollständige Praxisprojekte
15.1 Praxis 4: Persönliches Blog-System
Projektübersicht
Ein vollständiges Blog-System mit folgenden Funktionen:
Besucher-Seite:
- Artikeliste anzeigen
- Artikel-Detailseite
- Kategorien anzeigen
- Suche
Admin-Bereich:
- Artikel erstellen/bearbeiten/löschen
- Kategorien verwalten
- Login-System
Schritt 1: Datenbank vorbereiten
sql
-- Datenbank: blog_system
CREATE DATABASE blog_system
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
USE blog_system;
-- Tabelle: benutzer (für Admin-Login)
CREATE TABLE benutzer (
id INT AUTO_INCREMENT PRIMARY KEY,
benutzername VARCHAR(50) NOT NULL UNIQUE,
passwort VARCHAR(255) NOT NULL,
erstellt_am DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tabelle: kategorien
CREATE TABLE kategorien (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
beschreibung TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tabelle: artikel
CREATE TABLE artikel (
id INT AUTO_INCREMENT PRIMARY KEY,
titel VARCHAR(255) NOT NULL,
inhalt TEXT NOT NULL,
benutzer_id INT NOT NULL,
kategorie_id INT,
bild VARCHAR(255),
erstellt_am DATETIME DEFAULT CURRENT_TIMESTAMP,
aktualisiert_am DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (benutzer_id) REFERENCES benutzer(id) ON DELETE CASCADE,
FOREIGN KEY (kategorie_id) REFERENCES kategorien(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Beispieldaten einfügen
INSERT INTO benutzer (benutzername, passwort)
VALUES ('admin', PASSWORD_HASH('admin123', PASSWORD_DEFAULT));
INSERT INTO kategorien (name, beschreibung) VALUES
('Allgemein', 'Allgemeine Artikel'),
('Tutorials', 'Anleitungen und Tutorials'),
('News', 'Neuigkeiten');
INSERT INTO artikel (titel, inhalt, benutzer_id, kategorie_id) VALUES
('Willkommen auf meinem Blog', 'Dies ist mein erster Blog-Artikel. Viel Spaß beim Lesen!', 1, 1),
('PHP lernen', 'PHP ist eine großartige Sprache für Webentwicklung...', 1, 2);Schritt 2: Konfiguration und Hilfsfunktionen (config.php, functions.php)
php
<?php
// config.php
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'blog_system');
define('SITE_TITLE', 'Mein Blog');
define('SITE_URL', 'http://localhost/blog');
// Datenbankverbindung
function db_connect() {
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
die("Verbindungsfehler: " . $conn->connect_error);
}
$conn->set_charset("utf8mb4");
return $conn;
}
?>
<?php
// functions.php
require_once 'config.php';
// Funktion: Alle Artikel abfragen
function getAlleArtikel($limit = null) {
$conn = db_connect();
$sql = "SELECT a.*, b.benutzername, k.name as kategorie_name
FROM artikel a
LEFT JOIN benutzer b ON a.benutzer_id = b.id
LEFT JOIN kategorien k ON a.kategorie_id = k.id
ORDER BY a.erstellt_am DESC";
if ($limit !== null) {
$sql .= " LIMIT " . (int) $limit;
}
$result = $conn->query($sql);
$artikel = [];
while ($row = $result->fetch_assoc()) {
$artikel[] = $row;
}
$result->free();
$conn->close();
return $artikel;
}
// Funktion: Einzelnen Artikel abfragen
function getArtikel($id) {
$conn = db_connect();
$sql = "SELECT a.*, b.benutzername, k.name as kategorie_name
FROM artikel a
LEFT JOIN benutzer b ON a.benutzer_id = b.id
LEFT JOIN kategorien k ON a.kategorie_id = k.id
WHERE a.id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
$artikel = $result->fetch_assoc();
$stmt->close();
$conn->close();
return $artikel;
}
// Funktion: Alle Kategorien abfragen
function getAlleKategorien() {
$conn = db_connect();
$sql = "SELECT * FROM kategorien ORDER BY name";
$result = $conn->query($sql);
$kategorien = [];
while ($row = $result->fetch_assoc()) {
$kategorien[] = $row;
}
$result->free();
$conn->close();
return $kategorien;
}
// Funktion: Artikel kürzen (für Vorschau)
function kurzeText($text, $laenge = 200) {
if (strlen($text) <= $laenge) {
return $text;
}
return substr($text, 0, $laenge) . '...';
}
?>Schritt 3: Blog-Hauptseite (index.php)
php
<?php
// index.php
require_once 'functions.php';
$artikel = getAlleArtikel();
$kategorien = getAlleKategorien();
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo SITE_TITLE; ?></title>
<style>
body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
.header { background: #333; color: white; padding: 20px; text-align: center; }
.container { display: flex; margin-top: 20px; }
.main { flex: 3; padding-right: 20px; }
.sidebar { flex: 1; background: #f9f9f9; padding: 20px; }
.artikel { margin-bottom: 30px; border-bottom: 1px solid #ddd; padding-bottom: 20px; }
.artikel-titel { font-size: 24px; margin-bottom: 10px; }
.artikel-meta { font-size: 0.9em; color: #777; margin-bottom: 15px; }
.artikel-inhalt { line-height: 1.6; }
.kategorie-liste { list-style: none; padding: 0; }
.kategorie-liste li { margin-bottom: 10px; }
.kategorie-liste a { text-decoration: none; color: #333; }
.kategorie-liste a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="header">
<h1><?php echo SITE_TITLE; ?></h1>
<p>Ein Blog über Webentwicklung und mehr</p>
</div>
<div class="container">
<div class="main">
<h2>Aktuelle Artikel</h2>
<?php if (empty($artikel)): ?>
<p>Noch keine Artikel vorhanden.</p>
<?php else: ?>
<?php foreach ($artikel as $a): ?>
<div class="artikel">
<h3 class="artikel-titel">
<a href="artikel.php?id=<?php echo $a['id']; ?>"><?php echo htmlspecialchars($a['titel']); ?></a>
</h3>
<div class="artikel-meta">
Geschrieben von <?php echo htmlspecialchars($a['benutzername']); ?>
am <?php echo date("d.m.Y", strtotime($a['erstellt_am'])); ?>
<?php if ($a['kategorie_name']): ?>
| Kategorie: <?php echo htmlspecialchars($a['kategorie_name']); ?>
<?php endif; ?>
</div>
<div class="artikel-inhalt">
<?php echo nl2br(htmlspecialchars(kurzeText($a['inhalt'], 300))); ?>
</div>
<p><a href="artikel.php?id=<?php echo $a['id']; ?>">Weiterlesen →</a></p>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="sidebar">
<h3>Kategorien</h3>
<ul class="kategorie-liste">
<?php foreach ($kategorien as $k): ?>
<li>
<a href="kategorie.php?id=<?php echo $k['id']; ?>">
<?php echo htmlspecialchars($k['name']); ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<h3>Über dieses Blog</h3>
<p>Dies ist ein persönliches Blog-System, erstellt mit PHP und MySQL.</p>
<h3>Admin</h3>
<p><a href="admin/login.php">Admin-Login</a></p>
</div>
</div>
</body>
</html>Schritt 4: Artikel-Detailseite (artikel.php)
php
<?php
// artikel.php
require_once 'functions.php';
$artikel_id = (int) ($_GET['id'] ?? 0);
if ($artikel_id <= 0) {
header("Location: index.php");
exit();
}
$artikel = getArtikel($artikel_id);
if (!$artikel) {
header("Location: index.php");
exit();
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($artikel['titel']); ?> - <?php echo SITE_TITLE; ?></title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.artikel-titel { font-size: 32px; margin-bottom: 10px; }
.artikel-meta { font-size: 0.9em; color: #777; margin-bottom: 20px; border-bottom: 1px solid #ddd; padding-bottom: 20px; }
.artikel-inhalt { line-height: 1.8; font-size: 16px; }
.artikel-bild { max-width: 100%; height: auto; margin-bottom: 20px; }
.zurueck { margin-top: 30px; }
</style>
</head>
<body>
<p><a href="index.php">← Zurück zur Übersicht</a></p>
<h1 class="artikel-titel"><?php echo htmlspecialchars($artikel['titel']); ?></h1>
<div class="artikel-meta">
Geschrieben von <strong><?php echo htmlspecialchars($artikel['benutzername']); ?></strong>
am <strong><?php echo date("d.m.Y H:i", strtotime($artikel['erstellt_am'])); ?></strong>
<?php if ($artikel['kategorie_name']): ?>
| Kategorie: <strong><?php echo htmlspecialchars($artikel['kategorie_name']); ?></strong>
<?php endif; ?>
</div>
<?php if ($artikel['bild']): ?>
<img src="<?php echo htmlspecialchars($artikel['bild']); ?>" alt="<?php echo htmlspecialchars($artikel['titel']); ?>" class="artikel-bild">
<?php endif; ?>
<div class="artikel-inhalt">
<?php echo nl2br(htmlspecialchars($artikel['inhalt'])); ?>
</div>
<div class="zurueck">
<a href="index.php">← Zurück zur Übersicht</a>
</div>
</body>
</html>Schritt 5: Admin-Login (admin/login.php)
php
<?php
// admin/login.php
session_start();
require_once '../config.php';
$fehler = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$benutzername = trim($_POST['benutzername'] ?? '');
$passwort = $_POST['passwort'] ?? '';
if (empty($benutzername) || empty($passwort)) {
$fehler[] = "Benutzername und Passwort dürfen nicht leer sein.";
} else {
$conn = db_connect();
$sql = "SELECT id, benutzername, passwort FROM benutzer WHERE benutzername = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $benutzername);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows === 1) {
$stmt->bind_result($id, $benutzername_db, $passwort_hash);
$stmt->fetch();
if (password_verify($passwort, $passwort_hash)) {
session_regenerate_id(true);
$_SESSION['benutzer_id'] = $id;
$_SESSION['benutzername'] = $benutzername_db;
$_SESSION['admin_eingeloggt'] = true;
header("Location: dashboard.php");
exit();
} else {
$fehler[] = "Benutzername oder Passwort falsch.";
}
} else {
$fehler[] = "Benutzername oder Passwort falsch.";
}
$stmt->close();
$conn->close();
}
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Admin Login</title>
<style>
body { font-family: Arial, sans-serif; max-width: 400px; margin: 100px auto; padding: 20px; }
.fehler { color: red; }
</style>
</head>
<body>
<h1>🔐 Admin Login</h1>
<?php if (!empty($fehler)): ?>
<div class="fehler">
<ul>
<?php foreach ($fehler as $f): ?>
<li><?php echo htmlspecialchars($f); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form method="post" action="">
<label>Benutzername:<br>
<input type="text" name="benutzername" required>
</label><br><br>
<label>Passwort:<br>
<input type="password" name="passwort" required>
</label><br><br>
<button type="submit">Einloggen</button>
</form>
<p><a href="../index.php">← Zurück zum Blog</a></p>
</body>
</html>Schritt 6: Admin-Dashboard (admin/dashboard.php)
php
<?php
// admin/dashboard.php
session_start();
// Prüfen, ob Admin eingeloggt ist
if (!isset($_SESSION['admin_eingeloggt']) || $_SESSION['admin_eingeloggt'] !== true) {
header("Location: login.php");
exit();
}
require_once '../functions.php';
$artikel_anzahl = count(getAlleArtikel());
$kategorien_anzahl = count(getAlleKategorien());
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Admin Dashboard</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.header { background: #333; color: white; padding: 20px; }
.nav { background: #f2f2f2; padding: 10px; margin-top: 20px; }
.nav a { margin-right: 20px; text-decoration: none; }
.stats { display: flex; margin-top: 30px; }
.stat-box { flex: 1; background: #f9f9f9; padding: 20px; margin-right: 20px; text-align: center; }
.stat-zahl { font-size: 48px; font-weight: bold; color: #333; }
.stat-beschreibung { font-size: 14px; color: #777; }
</style>
</head>
<body>
<div class="header">
<h1>Admin Dashboard</h1>
<p>Willkommen, <?php echo htmlspecialchars($_SESSION['benutzername']); ?>!</p>
</div>
<div class="nav">
<a href="dashboard.php">Dashboard</a>
<a href="artikel.php">Artikel verwalten</a>
<a href="kategorien.php">Kategorien verwalten</a>
<a href="../index.php" target="_blank">Blog ansehen</a>
<a href="logout.php">Ausloggen</a>
</div>
<div class="stats">
<div class="stat-box">
<div class="stat-zahl"><?php echo $artikel_anzahl; ?></div>
<div class="stat-beschreibung">Artikel</div>
</div>
<div class="stat-box">
<div class="stat-zahl"><?php echo $kategorien_anzahl; ?></div>
<div class="stat-beschreibung">Kategorien</div>
</div>
</div>
<div style="margin-top: 30px;">
<h3>Schnellzugriff</h3>
<ul>
<li><a href="artikel.php?aktion=neu">Neuen Artikel erstellen</a></li>
<li><a href="artikel.php">Alle Artikel verwalten</a></li>
<li><a href="kategorien.php">Kategorien verwalten</a></li>
</ul>
</div>
</body>
</html>Schritt 7: Artikel verwalten (admin/artikel.php)
php
<?php
// admin/artikel.php
session_start();
if (!isset($_SESSION['admin_eingeloggt']) || $_SESSION['admin_eingeloggt'] !== true) {
header("Location: login.php");
exit();
}
require_once '../config.php';
require_once '../functions.php';
$aktion = $_GET['aktion'] ?? 'liste';
$erfolg = '';
$fehler = '';
// Artikel löschen
if ($aktion === 'loeschen' && isset($_GET['id'])) {
$id = (int) $_GET['id'];
$conn = db_connect();
$sql = "DELETE FROM artikel WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
$erfolg = "Artikel erfolgreich gelöscht!";
} else {
$fehler = "Fehler beim Löschen: " . $stmt->error;
}
$stmt->close();
$conn->close();
$aktion = 'liste';
}
// Artikel speichern (neu oder bearbeiten)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$titel = trim($_POST['titel'] ?? '');
$inhalt = trim($_POST['inhalt'] ?? '');
$kategorie_id = (int) ($_POST['kategorie_id'] ?? 0);
$artikel_id = (int) ($_POST['id'] ?? 0);
if (empty($titel) || empty($inhalt)) {
$fehler = "Titel und Inhalt dürfen nicht leer sein.";
} else {
$conn = db_connect();
if ($artikel_id > 0) {
// Artikel bearbeiten
$sql = "UPDATE artikel SET titel = ?, inhalt = ?, kategorie_id = ? WHERE id = ? AND benutzer_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ssiii", $titel, $inhalt, $kategorie_id, $artikel_id, $_SESSION['benutzer_id']);
} else {
// Neuen Artikel erstellen
$sql = "INSERT INTO artikel (titel, inhalt, benutzer_id, kategorie_id) VALUES (?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ssii", $titel, $inhalt, $_SESSION['benutzer_id'], $kategorie_id);
}
if ($stmt->execute()) {
$erfolg = "Artikel erfolgreich gespeichert!";
$aktion = 'liste';
} else {
$fehler = "Fehler beim Speichern: " . $stmt->error;
}
$stmt->close();
$conn->close();
}
}
// Artikel bearbeiten (Formular anzeigen)
if ($aktion === 'bearbeiten' && isset($_GET['id'])) {
$artikel_id = (int) $_GET['id'];
$artikel = getArtikel($artikel_id);
if (!$artikel || $artikel['benutzer_id'] != $_SESSION['benutzer_id']) {
$fehler = "Artikel nicht gefunden oder keine Berechtigung.";
$aktion = 'liste';
}
}
// Alle Artikel abfragen (für Liste)
if ($aktion === 'liste') {
$artikel_liste = getAlleArtikel();
$kategorien = getAlleKategorien();
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Artikel verwalten - Admin</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1000px; margin: 0 auto; padding: 20px; }
.header { background: #333; color: white; padding: 20px; }
.nav { background: #f2f2f2; padding: 10px; margin-top: 20px; }
.nav a { margin-right: 20px; text-decoration: none; }
.erfolg { color: green; }
.fehler { color: red; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f2f2f2; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input[type="text"], textarea, select { width: 100%; padding: 8px; }
textarea { height: 300px; }
</style>
</head>
<body>
<div class="header">
<h1>Artikel verwalten</h1>
</div>
<div class="nav">
<a href="dashboard.php">Dashboard</a>
<a href="artikel.php">Artikel verwalten</a>
<a href="artikel.php?aktion=neu">Neuen Artikel erstellen</a>
<a href="kategorien.php">Kategorien verwalten</a>
<a href="logout.php">Ausloggen</a>
</div>
<?php if ($erfolg): ?>
<p class="erfolg"><?php echo htmlspecialchars($erfolg); ?></p>
<?php endif; ?>
<?php if ($fehler): ?>
<p class="fehler"><?php echo htmlspecialchars($fehler); ?></p>
<?php endif; ?>
<?php if ($aktion === 'liste'): ?>
<h2>Alle Artikel</h2>
<p><a href="?aktion=neu">+ Neuen Artikel erstellen</a></p>
<?php if (empty($artikel_liste)): ?>
<p>Noch keine Artikel vorhanden.</p>
<?php else: ?>
<table>
<tr>
<th>Titel</th>
<th>Kategorie</th>
<th>Erstellt am</th>
<th>Aktionen</th>
</tr>
<?php foreach ($artikel_liste as $a): ?>
<tr>
<td><?php echo htmlspecialchars($a['titel']); ?></td>
<td><?php echo htmlspecialchars($a['kategorie_name'] ?? 'Keine'); ?></td>
<td><?php echo date("d.m.Y H:i", strtotime($a['erstellt_am'])); ?></td>
<td>
<a href="?aktion=bearbeiten&id=<?php echo $a['id']; ?>">Bearbeiten</a> |
<a href="?aktion=loeschen&id=<?php echo $a['id']; ?>" onclick="return confirm('Wirklich löschen?')">Löschen</a>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
<?php elseif ($aktion === 'neu' || $aktion === 'bearbeiten'): ?>
<h2><?php echo ($aktion === 'neu') ? 'Neuen Artikel erstellen' : 'Artikel bearbeiten'; ?></h2>
<form method="post" action="">
<?php if ($aktion === 'bearbeiten' && isset($artikel)): ?>
<input type="hidden" name="id" value="<?php echo $artikel['id']; ?>">
<?php endif; ?>
<div class="form-group">
<label>Titel:</label>
<input type="text" name="titel" required
value="<?php echo htmlspecialchars($artikel['titel'] ?? ''); ?>">
</div>
<div class="form-group">
<label>Inhalt:</label>
<textarea name="inhalt" required><?php echo htmlspecialchars($artikel['inhalt'] ?? ''); ?></textarea>
</div>
<div class="form-group">
<label>Kategorie:</label>
<select name="kategorie_id">
<option value="0">Keine Kategorie</option>
<?php foreach ($kategorien as $k): ?>
<option value="<?php echo $k['id']; ?>"
<?php echo (isset($artikel) && $artikel['kategorie_id'] == $k['id']) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($k['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<button type="submit">Speichern</button>
<a href="?aktion=liste">Abbrechen</a>
</div>
</form>
<?php endif; ?>
</body>
</html>15.2 Praxis 5: Einfaches CMS (Content Management System)
Ein CMS ist eine Erweiterung des Blog-Systems mit folgenden zusätzlichen Funktionen:
- Seiten verwalten (nicht nur Artikel)
- Medienverwaltung (Bilder hochladen)
- Benutzerrollen (Admin, Editor, Besucher)
- Einstellungen (Seitentitel, Footer-Text, etc.)
(Detaillierte Implementierung würde den Umfang dieses Kapitels überschreiten. Dies ist eine fortgeschrittene Übung für den Leser.)
Zusammenfassung
In diesem Kapitel haben Sie:
- ✅ Ein vollständiges Blog-System mit Datenbank, Artikelverwaltung und Admin-Bereich erstellt
- ✅ Funktionen wie Artikel erstellen, bearbeiten, löschen und anzeigen implementiert
- ✅ Ein Admin-Login-System mit Sessions integriert
- ✅ Die Grundkonzepte für ein einfaches CMS kennengelernt
Nächstes Kapitel: Wir werden Sicherheit und Optimierung lernen.
Übungsaufgaben:
- Erweitern Sie das Blog-System um eine Kommentarfunktion
- Fügen Sie eine Suche für Artikel hinzu
- Implementieren Sie eine Paginierung (Seitenaufteilung) für die Artikeliste
- Erweitern Sie das System zu einem vollständigen CMS mit Seitenverwaltung und Medienupload
Häufige Fehler:
- ❌
session_start()nach HTML-Ausgabe aufrufen → Muss ganz am Anfang stehen! - ❌ Keine Berechtigungsprüfung bei Admin-Aktionen → Jeder könnte Artikel löschen!
- ❌
strtotime()bei deutschen Datumsformaten verwenden → Nur englische Formate! - ❌
exit()nachheader("Location: ...")vergessen → Code wird weiter ausgeführt!
