About This Demo
From Academic to Professional
From academic MVC to professional web architecture
💡 Professional means: understandable, testable, extensible,
securely structured
Development of a modular mini-framework as a long-term project foundation
Goal
This project serves as an architectural reference for developing scalable web applications. It demonstrates the gradual transition from an academic MVC structure to a professional, modular web architecture.
The final result is not a single project, but a lightweight mini-framework that:
- Is understandable, testable, extensible, and securely structured.
- Serves as a stable foundation for any web projects
- Avoids later complete refactors
"Build the right chassis once – then just swap modules."
Starting point
Several already implemented demo projects serve as a basis (including MVC, Sessions, Login, modular CSS).
These projects are functionally well-constructed, but rely heavily on functions and are primarily suitable for smaller applications. As project size increases, they reach their limits in terms of structure, maintainability, and extensibility.
To control this transition, I created 2 projects, one could say in 2 phases:
- Phase 1: Enhancement of a classic academic MVC project
- Phase 2: Professionalization of my demo login project and taking it to the next level.
Stages
👉 Each stage functions independently
👉 No "start over" moments
| STAGE | FOCUS | GOAL | PHASE |
|---|---|---|---|
| 1 | Academic MVC | Understanding (starting point) | 1 |
| 2 | Folder structure & Service | Separate responsibilities & Overview | 1 |
| 3 | Index relief & Request flow | Bootstrap & Router | 1 |
| 4 | .htaccess configuration | Access security (foundation) | 1 |
| 5 | Adjustments for Phase 2 | Modularity & Maintainability | 2 |
| 6 | Modular CSS | Maintainability & Scalability | 2 |
| 7 | Classes & Navigation | Structure & Testability | 2 |
| 8 | Mini-Framework | Long-term basis | 2 |
The table shows the structured transition from simple concepts to professional architecture.
Structure
Goal: Separate responsibilities, security and clarity, without refactor
Principle: Only public/ is publicly accessible – everything else is protected.
10_Professional_web/ ├─ app/ │ ├─ config/ │ ├─ control/ │ ├─ helper/ │ ├─ model/ │ ├─ router/ │ ├─ security/ │ ├─ service/ │ ├─ view/ │ └─ bootstrap.php │ └─ router.php │ └─ session.php ├─ doc/... ├─ public/ ← only public │ ├─ css/ │ └─ img/ │ ├─ js/ │ ├─ index.php ← Single Entry Point ├─ sql/ ├─ storage/ │ ├─ logs/ │ └─ upload/ └─ README.md
Index relief
Problem
In academic MVC, index.php takes on too many tasks:
- Router
- Controller
- Security gate
- Workflow orchestrator
👉 Single point of chaos
Goal
index.php becomes
- Starting point
- no logic
Solution
- Outsource router
- Central Bootstrap logic
- Preparation for autoloading
- Clear request pipeline
Request pipeline
Request ↓ public/index.php ↓ Bootstrap ↓ Router ↓ Controller ↓ Service ↓ View / Response
Bootstrap
Initializes:
- Config
- Session
- Helper
- Controller & Service
- Model & View
Router
Task:
- URL → Controller / Action
- Extract parameters
- Handle errors cleanly
Implement
- Create: app/bootstrap.php
- Move import paths from index to new bootstrap
- Move switch($page) into controller in an embedded function.
- Move controller contents to Service folder and distribute to new files:
App/services/ │ ├─ AuthService.php │ ├─ UserService.php │ ├─ ProfileService.php │ ├─ SessionService.php │ └─ UploadService.php
- Create: app/router.php
- Move language and page check from session.php to router.php
Secure access with .htaccess
Two-file architecture
For access control, a two-file architecture with .htaccess is used. The file in the root directory controls external access and directs requests specifically to the public folder. The .htaccess in the public directory handles application routing.
This separation ensures a multi-layered security concept, clear responsibilities, and better maintainability.
Adjustments for Phase 2
Phase 2 serves to prepare the existing architecture in a targeted manner for growth, increasing complexity, and clean resource management.
Config split
app/config/ ├── config.php # Main configuration ├── app/ # Application configuration │ ├── form.config.php # Form configurations │ └── app.config.php # App settings └── domain/ # Domain-specific configs │ ├── page.config.php # Page configuration │ └── user.config.php # User management config └── security/ # Placeholder for security
- 🎯 Targeted loading: Only necessary configurations loaded
- 📁 Better organization: Thematic grouping
- 🧩 Modularity: Easy integration into services
Modular CSS
In the next step, modular CSS was integrated into the project. An existing demo project served as a basis, which could be adopted with little effort and inserted into the app/css/ directory. The existing structure was then slightly adapted and optimized to integrate cleanly into the rest of the architecture.
To integrate modular CSS professionally into the application, styles are not delivered directly from the App area. Instead, the CSS is dynamically generated in the public/css/ directory. This allows it to be included in the header like a classic stylesheet:
<link rel="stylesheet" href="css/style.php?page=<?php echo htmlspecialchars($_SESSION[S_PAGE]); ?>">
The style.php file serves as a controlled entry point. It includes internal CSS modules, prevents direct access to internal files, and ensures that CSS is correctly delivered as a stylesheet. Additionally, unwanted caching is prevented so that changes take effect immediately:
// Define security constant to prevent direct access to include files
define("SECURE_INCLUDE", true);
// Include main CSS file
require_once __DIR__ . "/../../app/css/main.css.php";
// Declare file as CSS and prevent cache
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");
// Determine page via GET parameter
$page = $_GET["page"] ?? "index";
if (!in_array($page, P_LIST, true)) {
$page = "index";
}
echo generateCSS(renderCSS($page));This solution combines the advantages of modular CSS with a clear separation between internal structure and public delivery and integrates seamlessly into the existing project architecture.
Classes
Classes are used in the project where they provide clear added value. This includes shared data with associated logic, more complex dependencies, as well as areas where testability and extensibility are important. Not every function is necessarily converted into a class.
Views remain pure templates, and simple helpers may deliberately remain as functions.
Session
The session logic was completely outsourced into its own class. For this, the cSession class was created, which centrally handles the startup and basic initialization of the session:
class cSession {
public static function start(): void {
session_start();
self::initDefaults();
self::initCsrfToken();
self::sanitizeSession();
}
// and these functions created:
initDefaults
initCsrfToken
sanitizeSession
requireAccess
isGuest
getRole
getUserId
increaseLoginAttempts
isRateLimited
The check_user_access function was removed from the Session class and moved to AuthService. The role check at the start of the session was also outsourced to the authentication area, bundled in the auth_init function and called centrally in the Bootstrap.
Additionally, an AdminService was introduced. The check of which user may be edited was removed from the session and encapsulated in its own set_edit_user_id function, which is also executed at startup in the Bootstrap.
As part of the restructuring, function calls were standardized, for example from handleSessionTimer() to cSession::increaseLoginAttempts() as well as from isSessionLimited() to isRateLimited(). Finally, outdated files were removed, specifically session.php in the app directory as well as session_service.php in the Service area.
Router
The router was also outsourced into its own class. The cRouter class handles the central initialization of routing and takes care of language and page recognition:
class cRouter {
public static function init(): void {
self::resolveLanguage();
self::resolvePage();
}
}The previous logic for language and page recognition was removed from the Session context and transferred into two clearly separated functions within the router. Thus, the responsibility for routing lies entirely with the router, while the Session class focuses on its actual tasks.
Database
For database connection, a general class was created that bundles recurring tasks and simplifies interaction with the database. The cDatabase class encapsulates connection establishment as well as frequently needed query and execution operations:
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 {…}
}Through this central abstraction, database accesses are implemented uniformly and existing functions are simplified, as recurring logic only needs to be implemented once. Additional consistency approaches improve maintainability, reduce error sources, and form a stable foundation for extensions.
Mini-Framework
By combining the previously introduced concepts, a lightweight
mini-framework emerges from the original MVC project.
Bootstrap, Router, central security rules, Service layer, modular
configuration as well as clearly separated responsibilities together
form a stable architecture.
This mini-framework does not completely replace large frameworks,
but serves as a controlled, understandable basis for your own web projects.
It enables clean extensions, avoids later structural breaks, and allows
new applications on a proven foundation.
👉 The project does not end with a single application, but with a reusable architecture.