Professionelle Webseite

Über diese Demo

Von Schulischem zum professionellem

Von schulischem MVC zur professionellen Webarchitektur
💡 Professionell heißt: verständlich, testbar, erweiterbar, sicher strukturiert

Entwicklung eines modularen Mini-Frameworks als langfristige Projektbasis

Ziel

Dieses Projekt dient als architektonische Referenz für die Entwicklung skalierbarer Webanwendungen. Es zeigt den schrittweisen Übergang von einer schulischen MVC-Struktur zu einer professionellen, modularen Webarchitektur.

Das Endergebnis ist kein einzelnes Projekt, sondern ein leichtgewichtiges Mini-Framework, das:

  • Verständlich, testbar, erweiterbar und sicher strukturiert ist.
  • Als stabile Basis für beliebige Webprojekte dient
  • Spätere Komplett-Refactors vermeidet

„Einmal das richtige Fahrgestell bauen – danach nur noch Module austauschen."

Ausgangspunkt

Als Grundlage dienen mehrere bereits umgesetzte Demoprojekte (u. a. MVC, Sessions, Login, modulares CSS).

Diese Projekte sind funktional sauber aufgebaut, basieren jedoch stark auf Funktionen und sind primär für kleinere Anwendungen geeignet. Bei zunehmender Projektgröße stoßen sie hinsichtlich Struktur, Wartbarkeit und Erweiterbarkeit an ihre Grenzen.

Um diesen Übergang kontrolliert zu gestalten, habe ich 2 Projekte angelegt, man könnte sagen in 2 Phasen:

  • Phase 1: Weiterentwicklung eines klassischen schulischen MVC-Projekts
  • Phase 2: Professionalisierung meines Demologin-Projekts und es aufs nächste Level bringen.

Etappen

👉 Jede Etappe funktioniert eigenständig
👉 Keine „Alles neu"-Momente

ETAPPEFOKUSZIELPHASE
1 Schulisches MVC Verständnis (Ausgangspunkt)1
2 Ordnerstruktur & ServiceVerantwortung trennen & Übersicht 1
3Index Entlastung & Request-FlowBootstrap & Rooter1
4 .htaccess-KonfigurationZugangssicherheit (Grundlage) 1
5 Anpassungen für Phase 2Modular & Wartbarkeit 2
6 Modulares CSSWartbarkeit & Skalierbarkeit 2
7 Klassen & Navigation Struktur & Testbarkeit2
8 Mini-FrameworkLangfristige Basis2

Die Tabelle zeigt den strukturierten Übergang von einfachen Konzepten zu professioneller Architektur.

Struktur

Ziel: Verantwortungen trennen, Sicherheit und Übersicht, ohne Refactor

Prinzip: Nur public/ ist öffentlich erreichbar – alles andere geschützt.

10_Professional_web/
 ├─ app/
 │  ├─ config/
 │  ├─ control/
 │  ├─ helper/
 │  ├─ model/
 │  ├─ router/
 │  ├─ security/
 │  ├─ service/
 │  ├─ view/
 │  └─ bootstrap.php
 │  └─ router.php
 │  └─ session.php
 ├─ doc/...
 ├─ public/          ← einzig öffentlich
 │  ├─ css/
 │  └─ img/
 │  ├─ js/
 │  ├─ index.php     ← Single Entry Point
 ├─ sql/
 ├─ storage/
 │  ├─ logs/
 │  └─ upload/
 └─ README.md

Entlastung des Index

Problem

Im schulischen MVC übernimmt index.php zu viele Aufgaben:

  • Router
  • Controller
  • Security-Gate
  • Workflow-Orchestrator

👉 Single Point of Chaos

Ziel

index.php wird

  • Startpunkt
  • keine Logik

Lösung

  • Router auslagern
  • Zentrale Bootstrap-Logik
  • Vorbereitung für Autoloading
  • klare Request-Pipeline

Request-Pipeline

Requestpublic/index.phpBootstrapRouterControllerServiceView / Response

Bootstrap

Initialisiert:

  • Config
  • Session
  • Helper
  • Controller & Service
  • Model & View

Router

Aufgabe:

  • URL → Controller / Aktion
  • Parameter extrahieren
  • Fehler sauber behandeln

Umsetzen

  • Erstelle: app/bootstrap.php
  • Verschiebe die Importpfade vom index in den neuen bootstrap
  • Verschiebe den switch($page) in Controller in einer Funktion eingebettet.
  • Verschiebe Controller Inhalte in Service Ordner und teile es auf die neuen Dateien auf:
App/services/
 │  ├─ AuthService.php
 │  ├─ UserService.php
 │  ├─ ProfileService.php
 │  ├─ SessionService.php
 │  └─ UploadService.php
  • Erstelle: app/router.php
  • Verschiebe Sprach- und Seiten-Prüfung von session.php zu router.php

Sicherer Zugang mit .htaccess

Zwei-Datei-Architektur

Für die Zugriffskontrolle wird eine Zwei-Dateien-Architektur mit .htaccess eingesetzt. Die Datei im Root-Verzeichnis steuert den externen Zugriff und leitet Anfragen gezielt in den public-Ordner weiter. Die .htaccess im public-Verzeichnis übernimmt das Application-Routing.

Diese Trennung sorgt für ein mehrschichtiges Sicherheitskonzept, klare Zuständigkeiten und eine bessere Wartbarkeit.

Anpassungen für Phase 2

Phase 2 dient dazu, die bestehende Architektur gezielt auf Wachstum, steigende Komplexität und sauberes Ressourcenmanagement vorzubereiten.

Config Aufteilung

app/config/
├── config.php # Hauptkonfiguration
├── app/ # Anwendungskonfiguration
│    ├── form.config.php # Formular-Konfigurationen
│    └── app.config.php # App-Einstellungen
└── domain/ # Domain-spezifische Konfigs
│    ├── page.config.php # Seiten-Konfiguration
│    └── user.config.php # User-Management-Konfig
└── security/ # Platzhalter für Sicherheit
  • 🎯 Zielgerichtetes Laden: Nur notwendige Konfigurationen laden
  • 📁 Bessere Organisation: Thematische Gruppierung
  • 🧩 Modularität: Einfache Integration in Services

Modulares CSS

Im nächsten Schritt wurde modulares CSS in das Projekt integriert. Als Grundlage diente ein bereits vorhandenes Demo-Projekt, das sich mit geringem Aufwand übernehmen und in das Verzeichnis app/css/ einfügen ließ. Die bestehende Struktur wurde anschließend leicht angepasst und optimiert, sodass sie sauber in die restliche Architektur eingebettet ist.

Um das modulare CSS professionell in die Anwendung einzubinden, werden die Styles nicht direkt aus dem App-Bereich ausgeliefert. Stattdessen wird das CSS im Verzeichnis public/css/ dynamisch generiert. Dadurch kann es wie eine klassische Stylesheet-Datei im Header eingebunden werden:

<link rel="stylesheet" href="css/style.php?page=<?php echo htmlspecialchars($_SESSION[S_PAGE]); ?>">

Die Datei style.php dient dabei als kontrollierter Einstiegspunkt. Sie bindet die internen CSS-Module ein, verhindert direkten Zugriff auf interne Dateien und sorgt dafür, dass das CSS korrekt als Stylesheet ausgeliefert wird. Zusätzlich wird unerwünschtes Caching unterbunden, sodass Änderungen sofort wirksam sind:

// Schutz-Konstante definieren, um direkten Zugriff auf include-Dateien zu verhindern
define("SECURE_INCLUDE", true);

// CSS-Hauptdatei einbinden
require_once __DIR__ . "/../../app/css/main.css.php";

// Datei als CSS deklarieren und Cache verhindern
header("Content-Type: text/css; charset=UTF-8");
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache");
header("X-Content-Type-Options: nosniff");

// Seite über GET-Parameter ermitteln
$page = $_GET["page"] ?? "index";
if (!in_array($page, P_LIST, true)) {
    $page = "index";
}

echo generateCSS(renderCSS($page));

Diese Lösung verbindet die Vorteile von modularem CSS mit einer klaren Trennung zwischen interner Struktur und öffentlicher Auslieferung und fügt sich nahtlos in die bestehende Projektarchitektur ein.

Klassen

Klassen werden im Projekt gezielt dort eingesetzt, wo sie einen klaren Mehrwert bieten. Dazu zählen gemeinsame Daten mit zugehöriger Logik, komplexere Abhängigkeiten sowie Bereiche, in denen Testbarkeit und Erweiterbarkeit wichtig sind. Nicht jede Funktion wird zwangsläufig in eine Klasse überführt.

Views bleiben weiterhin reine Templates, und einfache Helper dürfen bewusst als Funktionen bestehen bleiben.

Session

Die Session-Logik wurde vollständig in eine eigene Klasse ausgelagert. Dafür wurde die Klasse cSession erstellt, welche den Start und die grundlegende Initialisierung der Session zentral übernimmt:

class cSession {
    public static function start(): void {

        session_start();

        self::initDefaults();
        self::initCsrfToken();
        self::sanitizeSession();
    }
    // und diese Funktionen erstellt:
    initDefaults
    initCsrfToken
    sanitizeSession
    requireAccess
    isGuest
    getRole
    getUserId
    increaseLoginAttempts
    isRateLimited

// Routen laden (Route → Callable) $routes = require CONTROL_DIR . 'routes.php'; // Fallback, falls Route nicht existiert if (!isset($routes[$page]) || !is_callable($routes[$page])) { errorLog(__FILE__, __LINE__, __FUNCTION__, "Unknown route: {$page}"); reload_page(P_HOME); } // Route ausführen (Controller-Action) $routes[$page]($labels);

Zusätzlich wurde ein AdminService eingeführt. Die Prüfung, welcher Benutzer bearbeitet werden darf, wurde aus der Session entfernt und in einer eigenen Funktion set_edit_user_id gekapselt, die ebenfalls beim Start im Bootstrap ausgeführt wird.

Im Zuge der Umstrukturierung wurden Funktionsaufrufe vereinheitlicht, zum Beispiel von handleSessionTimer() zu cSession::increaseLoginAttempts() sowie von isSessionLimited() zu isRateLimited(). Abschließend wurden veraltete Dateien entfernt, konkret session.php im app-Verzeichnis sowie session_service.php im Service-Bereich.

Router

Auch der Router wurde in eine eigene Klasse ausgelagert. Die Klasse cRouter übernimmt die zentrale Initialisierung des Routings und kümmert sich um die Erkennung von Sprache und Seite:

class cRouter {
    public static function init(): void {
        self::resolveLanguage();
        self::resolvePage();
    }
}

Die bisherige Logik zur Sprach- und Seitenerkennung wurde aus dem Session-Kontext entfernt und in zwei klar getrennte Funktionen innerhalb des Routers überführt. Dadurch liegt die Verantwortung für das Routing vollständig beim Router, während sich die Session-Klasse auf ihre eigentlichen Aufgaben konzentriert.

Datenbank

Für die Datenbankanbindung wurde eine allgemeine Klasse erstellt, die wiederkehrende Aufgaben bündelt und die Interaktion mit der Datenbank vereinfacht. Die Klasse cDatabase kapselt den Aufbau der Verbindung sowie häufig benötigte Abfrage- und Ausführungsoperationen:

class cDatabase {
    private static ?PDO $dbh = null;
    public static function getConnection(): PDO {…}
    public static function close(): void {…}
    public static function fetchAll(…): array {…    }
    public static function fetchOne(…): array|false {…}
    public static function execute(…): bool {…}
}

Durch diese zentrale Abstraktion werden Datenbankzugriffe einheitlich umgesetzt und bestehende Funktionen vereinfacht, da wiederkehrende Logik nur noch einmal implementiert werden muss. Ergänzende Konsistenzansätze verbessern die Wartbarkeit, reduzieren Fehlerquellen und bilden eine stabile Grundlage für Erweiterungen.

Navigation

Problem

Unterschiedliche Navigation je nach:

  • Seite
  • Benutzerrolle
  • Login-Status

Lösung

  • Für jede Seite werden erlaubte Rollen definiert
  • Die Sichtbarkeit von Seiten wird über zentrale Regeln gesteuert
  • Eine Filterfunktion ersetzt verstreute if-Abfragen

Umsetzung

Zunächst wurde eine zentrale Security-Config-Datei erstellt. Darin definiert eine Konstanten-Tabelle die Sicherheitsregeln und Zugriffsberechtigungen für jede Seite. Die bisherigen, seitenbezogenen Prüfungen im Controller wurden entfernt und durch eine einmalige, zentrale Überprüfung zu Beginn der Seitenverarbeitung ersetzt. Dadurch wird die Logik übersichtlicher und redundante Prüfungen entfallen.

Anschließend wurde eine eigenständige, abrufbare Routen-Datei eingeführt. Diese Datei gibt ein Array zurück, das die bisherige switch-Logik aus dem Route-Controller ersetzt. Die Seiten sind als Schlüssel definiert und verweisen jeweils auf anonyme Funktionen, die die bisherigen handle*- und renderView*-Aufrufe enthalten. Nach der Anpassung konnte der ursprüngliche switch vollständig entfernt und das neue Routing erfolgreich geladen, getestet und ausgeführt werden:

Zusätzlich wurde eine Funktion implementiert, die auf Basis der definierten Sicherheitsregeln automatisch eine Liste von Seiten ermittelt, die auf eine bestimmte Seite verlinken dürfen. Diese Liste dient als Grundlage für die Navigation. Die bestehende Navigationslogik wurde daraufhin überarbeitet, vereinfacht und nutzt nun ausschließlich die dynamisch generierte Navigationsliste.

Mini-Framework

Durch die Kombination der zuvor eingeführten Konzepte entsteht aus dem ursprünglichen MVC-Projekt ein leichtgewichtiges Mini-Framework.
Bootstrap, Router, zentrale Security-Regeln, Service-Schicht, modulare Konfiguration sowie klar getrennte Verantwortlichkeiten bilden gemeinsam eine stabile Architektur.

für eigene Webprojekte.
Es ermöglicht saubere Erweiterungen, vermeidet spätere Strukturbrüche und erlaubt es, neue Anwendungen auf einem bewährten Grundgerüst aufzubauen.

👉 Das Projekt endet nicht mit einer einzelnen Anwendung, sondern mit einer wiederverwendbaren Architektur.