1
0
Files
breadcrumb-the-shire/docs/ARCHITECTURE.md
2026-02-04 23:31:53 +01:00

20 KiB

IMVS - Architektur und Grundprinzipien

Inhaltsverzeichnis

  1. Projektübersicht
  2. Framework: MintyPHP
  3. Verzeichnisstruktur
  4. Datenbankarchitektur
  5. PHP-Architektur
  6. Frontend-Architektur
  7. Template-System
  8. Authentifizierung und Autorisierung
  9. Internationalisierung
  10. Best Practices

1. Projektübersicht

Projektname: IMVS Framework: MintyPHP v3+ PHP-Version: 8.5+ Datenbank: MySQL 5.7+ Lizenz: MIT

Kernfeatures

  • Multi-Tenant-Architektur
  • Rollen- und Berechtigungssystem
  • Mehrsprachigkeit (DE/EN)
  • Dark/Light Theme
  • E-Mail-Integration mit Templates

2. Framework: MintyPHP

Philosophie

MintyPHP ist ein leichtgewichtiges PHP-Framework mit folgenden Prinzipien:

  • Easy to learn - Einfach für PHP-Entwickler zu erlernen
  • Secure by design - Sicherheit als Grundprinzip
  • Lightweight - Minimaler Overhead
  • Single variable scope - Eine Variable-Scope für alle Layer
  • SQL required - Direktes SQL statt ORM
  • PHP as templating - PHP selbst als Template-Sprache

Kern-Komponenten

Komponente Beschreibung
Router URL-Routing zu Pages
DB Datenbankzugriff mit Prepared Statements
Auth Basis-Authentifizierung
Session Session-Management
Buffer Output-Buffering für Templates
I18n Internationalisierung
Cache Memcached-Integration
Firewall DDoS-Schutz

Router-Konvention

URL: /admin/users/edit/abc-123
Datei: pages/admin/users/edit($id).php
Variable: $id = 'abc-123'

3. Verzeichnisstruktur

ImmobilienMaklerVerkaufssoftware/
├── config/                 # Konfigurationsdateien
│   ├── config.php         # Hauptkonfiguration
│   ├── router.php         # Route-Definitionen
│   └── settings.php       # App-Einstellungen (generiert)
├── db/                     # Datenbank
│   └── init/init.sql      # Schema und Seeds
├── docker/                 # Docker-Konfiguration
├── docs/                   # Dokumentation
├── i18n/                   # Sprachdateien
│   ├── default_de.json
│   └── default_en.json
├── lib/                    # Geschäftslogik
│   ├── Http/              # HTTP-Utilities
│   ├── Repository/        # Datenbankschicht
│   ├── Service/           # Business-Logic
│   └── Support/           # Helper und Guards
├── pages/                  # Seiten (Controller)
│   ├── admin/             # Geschützte Admin-Seiten
│   ├── auth/              # Authentifizierung
│   ├── error/             # Fehlerseiten
│   └── page/              # CMS-Seiten
├── storage/                # Dateiablage
│   ├── branding/          # Logos, Favicons
│   ├── tenants/           # Tenant-Dateien
│   └── users/             # User-Avatare
├── templates/              # View-Templates
│   ├── partials/          # Wiederverwendbare Teile
│   └── emails/            # E-Mail-Templates
├── tests/                  # PHPUnit Tests
├── vendor/                 # Composer Dependencies
└── web/                    # Public Root
    ├── css/               # Stylesheets
    ├── js/                # JavaScript-Module
    ├── vendor/            # Frontend-Libraries
    └── index.php          # Entry Point

4. Datenbankarchitektur

Entity-Relationship-Diagramm

┌─────────────┐     ┌─────────────────┐     ┌─────────────┐
│   USERS     │────<│  USER_TENANTS   │>────│   TENANTS   │
└─────────────┘     └─────────────────┘     └─────────────┘
      │                                            │
      │             ┌─────────────────┐            │
      └────────────<│  USER_ROLES     │            │
      │             └─────────────────┘            │
      │                    │                       │
      │                    ▼                       │
      │             ┌─────────────┐                │
      │             │    ROLES    │                │
      │             └─────────────┘                │
      │                    │                       │
      │             ┌─────────────────┐            │
      │             │ROLE_PERMISSIONS │            │
      │             └─────────────────┘            │
      │                    │                       │
      │                    ▼                       │
      │             ┌─────────────┐                │
      │             │ PERMISSIONS │                │
      │             └─────────────┘                │
      │                                            │
      │             ┌─────────────────┐     ┌──────┴──────┐
      └────────────<│USER_DEPARTMENTS │>────│ DEPARTMENTS │
                    └─────────────────┘     └─────────────┘
                                                   │
                                            ┌──────┴──────┐
                                            │TENANT_DEPTS │
                                            └─────────────┘

Haupttabellen

users

id              INT PRIMARY KEY AUTO_INCREMENT
uuid            VARCHAR(36) UNIQUE
first_name      VARCHAR(100)
last_name       VARCHAR(100)
email           VARCHAR(255) UNIQUE
password        VARCHAR(255)          -- bcrypt hash
locale          VARCHAR(10)           -- 'de', 'en'
theme           VARCHAR(20)           -- 'light', 'dark'
active          TINYINT DEFAULT 1
primary_tenant_id  INT FK -> tenants.id
current_tenant_id  INT FK -> tenants.id
created_by      INT FK -> users.id
modified_by     INT FK -> users.id
created         DATETIME
modified        DATETIME

tenants

id              INT PRIMARY KEY AUTO_INCREMENT
uuid            VARCHAR(36) UNIQUE
description     VARCHAR(255)          -- Name/Firma
address         VARCHAR(255)
postal_code     VARCHAR(20)
city            VARCHAR(100)
country         VARCHAR(100)
vat_id          VARCHAR(50)           -- MwSt-ID
tax_number      VARCHAR(50)
phone           VARCHAR(50)
email           VARCHAR(255)
website         VARCHAR(255)
created_by      INT FK -> users.id
modified_by     INT FK -> users.id
created         DATETIME
modified        DATETIME

roles

id              INT PRIMARY KEY AUTO_INCREMENT
uuid            VARCHAR(36) UNIQUE
description     VARCHAR(255)          -- Rollenname
created_by      INT FK -> users.id
modified_by     INT FK -> users.id
created         DATETIME
modified        DATETIME

permissions

id              INT PRIMARY KEY AUTO_INCREMENT
key             VARCHAR(100) UNIQUE   -- z.B. 'users.view'
description     VARCHAR(255)
created         DATETIME

departments

id              INT PRIMARY KEY AUTO_INCREMENT
uuid            VARCHAR(36) UNIQUE
description     VARCHAR(255)          -- Abteilungsname
created_by      INT FK -> users.id
modified_by     INT FK -> users.id
created         DATETIME
modified        DATETIME

Verknüpfungstabellen

Tabelle Beschreibung
user_tenants User ↔ Tenant (M:N)
user_roles User ↔ Role (M:N)
user_departments User ↔ Department (M:N)
role_permissions Role ↔ Permission (M:N)
tenant_departments Tenant ↔ Department (M:N)

Vordefinierte Permissions

users.view, users.create, users.update, users.delete
users.self_update, users.update_assignments
tenants.view, tenants.create, tenants.update, tenants.delete
departments.view, departments.create, departments.update, departments.delete
roles.view, roles.create, roles.update, roles.delete
permissions.view, permissions.create, permissions.update, permissions.delete
settings.view, settings.update
mail_log.view
stats.view

5. PHP-Architektur

Schichten-Architektur

┌─────────────────────────────────────────────┐
│                  Pages                       │
│         (Controller/Actions)                 │
├─────────────────────────────────────────────┤
│                 Services                     │
│      (Business Logic, Validation)           │
├─────────────────────────────────────────────┤
│               Repositories                   │
│         (Data Access, SQL)                  │
├─────────────────────────────────────────────┤
│                   DB                         │
│          (MintyPHP Database)                │
└─────────────────────────────────────────────┘

Service-Layer (lib/Service/)

Services implementieren die Geschäftslogik:

namespace MintyPHP\Service;

class UserService
{
    // Listen
    public static function list(): array
    public static function listPaged(array $options): array

    // Finder
    public static function findByUuid(string $uuid): ?array
    public static function findById(int $id): ?array
    public static function findByEmail(string $email): ?array

    // CRUD
    public static function createFromAdmin(array $input, int $currentUserId): array
    public static function updateFromAdmin(int $userId, array $input, int $currentUserId): array
    public static function deleteByUuid(string $uuid, int $currentUserId): array

    // Bulk-Operationen
    public static function deleteByUuids(array $uuids, int $currentUserId): array
    public static function setActiveByUuids(array $uuids, bool $active, int $currentUserId): array
}

Wichtige Services:

Service Verantwortung
AuthService Login, Logout, Session-Management
UserService Benutzerverwaltung
TenantService Mandantenverwaltung
RoleService Rollenverwaltung
PermissionService Berechtigungsprüfung und -verwaltung
DepartmentService Abteilungsverwaltung
MailService E-Mail-Versand
SettingService App-Einstellungen

Repository-Layer (lib/Repository/)

Repositories kapseln Datenbankzugriffe:

namespace MintyPHP\Repository;

class UserRepository
{
    public static function list(): array
    {
        return DB::select('SELECT * FROM users ORDER BY last_name, first_name');
    }

    public static function find(int $id): ?array
    {
        return DB::selectOne('SELECT * FROM users WHERE id = ?', [$id]);
    }

    public static function create(array $data): int
    {
        return DB::insert('users', $data);
    }

    public static function update(int $id, array $data): bool
    {
        return DB::update('users', $data, ['id' => $id]) > 0;
    }
}

Support-Layer (lib/Support/)

Guard - Zugriffskontrolle

namespace MintyPHP\Support;

class Guard
{
    // Nur für angemeldete Benutzer
    public static function requireLogin(string $redirect = 'login'): void

    // Spezifische Berechtigung erforderlich
    public static function requirePermission(string $permissionKey, string $redirect = 'admin'): void

    // Berechtigung oder 403 (für AJAX)
    public static function requirePermissionOrForbidden(string $permissionKey): void
}

Flash - Session-Nachrichten

namespace MintyPHP\Support;

class Flash
{
    public static function success(string $message, ?string $scope = null, ?string $key = null): string
    public static function error(string $message, ?string $scope = null, ?string $key = null): string
    public static function info(string $message, ?string $scope = null, ?string $key = null): string
    public static function peek(?string $scope = null): array
    public static function has(): bool
}

Helper-Funktionen (lib/Support/helpers/)

// HTML-Escape
e($string);

// Asset-URL
asset('css/style.css');

// Lokalisierte URL
lurl('admin/users');

// Übersetzung
t('Hello');
t('Welcome %s', $name);

// App-Einstellung
appSetting('app_title');

// Berechtigung prüfen
can('users.view');

6. Frontend-Architektur

CSS-Struktur (web/css/)

web/css/
├── base/
│   └── variables.css       # CSS-Variablen
├── layout/
│   ├── app-shell.css       # Haupt-Layout
│   ├── app-topbar.css      # Header
│   ├── app-sidebar.css     # Navigation
│   └── app-aside-icon-bar.css
├── components/
│   ├── app-buttons.css
│   ├── app-forms.css
│   ├── app-list-table.css
│   ├── app-badges.css
│   ├── app-tabs.css
│   ├── app-flash.css
│   └── vendor-*.css        # Third-Party
└── pages/
    └── app-login.css

JavaScript-Module (web/js/)

ES-Module-Architektur:

web/js/
├── app-init.js             # Haupt-Initialisierung
├── core/
│   └── app-grid-factory.js # Grid/Tabellen-Generator
├── components/
│   ├── app-tenant-switcher.js
│   ├── app-theme-toggle.js
│   ├── app-bulk-selection.js
│   ├── app-flash-auto-dismiss.js
│   └── ...
└── pages/
    ├── app-users-list.js
    └── app-list-utils.js

Modul-Pattern

// web/js/components/app-tenant-switcher.js
const switchTenant = async (link) => {
    const tenantId = link.dataset.switchTenant;
    // ...
};

const initTenantSwitcher = () => {
    const tenantLinks = document.querySelectorAll('[data-switch-tenant]');
    tenantLinks.forEach(link => {
        link.addEventListener('click', (e) => {
            e.preventDefault();
            switchTenant(link);
        });
    });
};

// Auto-Init bei DOM Ready
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initTenantSwitcher);
} else {
    initTenantSwitcher();
}

Import in app-init.js

// web/js/app-init.js
import './components/app-tenant-switcher.js';
import './components/app-theme-toggle.js';
import './components/app-flash-auto-dismiss.js';
// ...

7. Template-System

Struktur

templates/
├── default.phtml           # Haupt-Layout
├── login.phtml             # Login-Layout
├── error.phtml             # Fehler-Layout
├── partials/
│   ├── app-topbar.phtml
│   ├── app-sidebar.phtml
│   ├── app-aside.phtml
│   ├── app-flash.phtml
│   └── app-footer.phtml
└── emails/
    ├── de/
    │   ├── access_info.html
    │   └── reset_code.html
    └── en/
        └── ...

Template-Rendering

<!-- templates/default.phtml -->
<!DOCTYPE html>
<html lang="<?php e(I18n::$locale); ?>">
<head>
    <title><?php e(Buffer::get('title') ?? appTitle()); ?></title>
    <link rel="stylesheet" href="<?php e(asset('css/app.css')); ?>">
</head>
<body>
    <?php require 'partials/app-topbar.phtml'; ?>

    <main>
        <?php require 'partials/app-flash.phtml'; ?>
        <?php echo Buffer::get('html'); ?>
    </main>

    <script type="module" src="<?php e(asset('js/app-init.js')); ?>"></script>
</body>
</html>

Page mit View

// pages/admin/users/index().php
use MintyPHP\Buffer;
use MintyPHP\Support\Guard;

Guard::requirePermissionOrForbidden('users.view');

$users = UserService::list();

Buffer::set('title', t('Users'));

// View wird automatisch geladen: pages/admin/users/index(default).phtml

8. Authentifizierung und Autorisierung

Login-Ablauf

1. POST /login
   │
   ├─> AuthService::login(email, password)
   │   ├─> Auth::login() - Passwort-Verifikation
   │   ├─> $_SESSION['user'] setzen
   │   ├─> PermissionService::getUserPermissions()
   │   │   └─> $_SESSION['permissions'] setzen
   │   └─> AuthService::loadTenantDataIntoSession()
   │       ├─> $_SESSION['available_tenants']
   │       └─> $_SESSION['current_tenant']
   │
   └─> Router::redirect('admin')

Berechtigungsprüfung

// In Page
Guard::requirePermissionOrForbidden(PermissionService::USERS_VIEW);

// Im Template
<?php if (can('users.create')): ?>
    <a href="admin/users/create">Neuer Benutzer</a>
<?php endif; ?>

Session-Struktur

$_SESSION = [
    'user' => [
        'id' => 1,
        'uuid' => 'abc-123',
        'email' => 'user@example.com',
        'first_name' => 'Max',
        'last_name' => 'Mustermann',
        'locale' => 'de',
        'theme' => 'dark',
    ],
    'permissions' => [
        'user_id' => 1,
        'keys' => ['users.view', 'users.create', ...],
    ],
    'available_tenants' => [
        ['id' => 1, 'uuid' => '...', 'description' => 'Tenant A'],
        ['id' => 2, 'uuid' => '...', 'description' => 'Tenant B'],
    ],
    'current_tenant' => [
        'id' => 1,
        'uuid' => '...',
        'description' => 'Tenant A',
    ],
];

9. Internationalisierung

Sprachdateien

// i18n/default_de.json
{
    "Login": "Anmelden",
    "Users": "Benutzer",
    "At least %d characters": "Mindestens %d Zeichen"
}

// i18n/default_en.json
{
    "Login": "Login",
    "Users": "Users",
    "At least %d characters": "At least %d characters"
}

Verwendung

// Einfache Übersetzung
t('Login')

// Mit Parametern
t('At least %d characters', 12)

// Im Template
<?php e(t('Users')); ?>

Sprachauflösung

Priorität:

  1. URL-Präfix (/de/admin, /en/admin)
  2. Session ($_SESSION['user']['locale'])
  3. Cookie (locale=de)
  4. Default (APP_LOCALE)

10. Best Practices

Service-Rückgabewerte

// Erfolg
return [
    'ok' => true,
    'uuid' => $uuid,
    'id' => $id,
];

// Fehler
return [
    'ok' => false,
    'error' => 'validation_error',
    'errors' => ['email' => 'Email ist erforderlich'],
    'form' => $sanitizedInput,
];

Datenbankzugriff

// RICHTIG - Prepared Statements
$user = DB::selectOne('SELECT * FROM users WHERE email = ?', [$email]);

// FALSCH - SQL-Injection
$user = DB::selectOne("SELECT * FROM users WHERE email = '$email'");

Template-Ausgabe

// RICHTIG - Escape
<?php e($user['email']); ?>

// FALSCH - XSS-Risiko
<?php echo $user['email']; ?>

Passwort-Anforderungen

  • Mindestens 12 Zeichen
  • Mindestens 1 Großbuchstabe
  • Mindestens 1 Kleinbuchstabe
  • Mindestens 1 Ziffer
  • Mindestens 1 Sonderzeichen

Code-Organisation

  1. Services - Geschäftslogik und Validierung
  2. Repositories - Nur Datenbankzugriff, keine Logik
  3. Guards - Zugriffskontrolle
  4. Helpers - Wiederverwendbare Funktionen
  5. Pages - Koordination, keine Logik

Weiterführende Dokumentation