{
"tool_response": {
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/docs\/plan-code-quality-gates.md",
"oldString": "# Prüfung 3-6\n\n*Werden nach Abnahme von Prüfung 2 definiert.*",
"newString": "# Prüfung 3: PSR + Types\n\n## Ziel\n\n**PSR (PHP Standards Recommendations):**\n- PSR-1: Basic Coding Standard\n- PSR-4: Autoloading (Namespace = Pfad)\n- PSR-12: Extended Coding Style\n\n**Types (Typdisziplin):**\n- `declare(strict_types=1)` in jeder Datei\n- Typ-Deklarationen für Parameter\n- Return-Type-Deklarationen\n- Property-Types (PHP 7.4+)\n\n## Warum vor SOLID\/DIP?\n\nDIP (Dependency Inversion) basiert auf Interfaces.\nInterfaces ohne Typen sind wirkungslos.\nBeispiel:\n\n```php\n\/\/ Ohne Types: Interface ist Dekoration\ninterface UserRepository {\n public function find($id); \/\/ Was ist $id? Was kommt zurück?\n}\n\n\/\/ Mit Types: Interface ist Contract\ninterface UserRepository {\n public function find(int $id): ?User;\n}\n```\n\n**Types vor DIP ist zwingend.**\n\n## Grundsatz\n\nPSR und Types sind vollständig deterministisch prüfbar.\nKein semantischer Spielraum. Kein Kontext erforderlich.\nDaher: Alle Regeln sind Pre-Hook (BLOCK).\n\n---\n\n## Pre-Hook (BLOCK)\n\n### P3.1: strict_types erforderlich\n\n**Regel:** Jede PHP-Datei muss `declare(strict_types=1)` haben.\n\n**Pattern:**\n```python\nstrict_types_pattern = r\"declare\\s*\\(\\s*strict_types\\s*=\\s*1\\s*\\)\\s*;\"\nif not re.search(strict_types_pattern, content):\n block(\"Missing declare(strict_types=1)\")\n```\n\n**Position:** Muss in den ersten 5 Zeilen sein (nach <?php, vor namespace).\n\n**Begründung:**\n- Ohne strict_types ist PHP-Typisierung wirkungslos\n- Type Coercion versteckt Bugs\n- Grundlage für alle weiteren Type-Prüfungen\n\n---\n\n### P3.2: Namespace muss Pfad entsprechen (PSR-4)\n\n**Regel:** Der Namespace muss dem Dateipfad entsprechen.\n\n**Mapping:**\n```python\npath_to_namespace = {\n \"\/src\/Controller\/\": \"Controller\\\\\",\n \"\/src\/Domain\/\": \"Domain\\\\\",\n \"\/src\/UseCases\/\": \"UseCases\\\\\",\n \"\/src\/Application\/\": \"Application\\\\\",\n \"\/src\/Infrastructure\/\": \"Infrastructure\\\\\",\n \"\/src\/Framework\/\": \"Framework\\\\\",\n \"\/app\/\": \"App\\\\\",\n}\n```\n\n**Prüfung:**\n```python\nnamespace_match = re.search(r\"namespace\\s+([^;]+);\", content)\nif namespace_match:\n declared_namespace = namespace_match.group(1)\n expected_prefix = get_expected_prefix(file_path)\n if not declared_namespace.startswith(expected_prefix):\n block(f\"Namespace '{declared_namespace}' does not match path. Expected: '{expected_prefix}...'\")\n```\n\n**Begründung:**\n- PSR-4 Autoloading erfordert Pfad-Namespace-Konsistenz\n- Inkonsistenz führt zu Autoload-Fehlern\n- IDE-Navigation bricht\n\n---\n\n### P3.3: Klassenname muss Dateiname entsprechen (PSR-1)\n\n**Regel:** Der Klassenname muss dem Dateinamen entsprechen.\n\n**Prüfung:**\n```python\nclass_match = re.search(r\"(?:class|interface|trait|enum)\\s+(\\w+)\", content)\nif class_match:\n class_name = class_match.group(1)\n expected_name = Path(file_path).stem # Filename without .php\n if class_name != expected_name:\n block(f\"Class '{class_name}' does not match filename '{expected_name}'\")\n```\n\n**Begründung:**\n- PSR-1 Standard\n- Eine Klasse pro Datei\n- Dateiname = Klassenname\n\n---\n\n### P3.4: Public Methods müssen Return-Type haben\n\n**Regel:** Jede public function muss einen Return-Type deklarieren.\n\n**Pattern:**\n```python\n# Findet public functions ohne Return-Type\npublic_without_return = re.findall(\n r\"public\\s+function\\s+\\w+\\s*\\([^)]*\\)\\s*(?!:)\\s*\\{\",\n content\n)\nif public_without_return:\n block(f\"Public method(s) without return type declaration\")\n```\n\n**Ausnahmen:**\n- `__construct` (kein Return-Type erlaubt)\n- `__destruct` (kein Return-Type erlaubt)\n\n**Verfeinertes Pattern:**\n```python\npublic_methods = re.findall(\n r\"public\\s+function\\s+(\\w+)\\s*\\([^)]*\\)\\s*(?::\\s*\\S+)?\\s*\\{\",\n content\n)\nfor method in public_methods:\n if method.startswith(\"__\"):\n continue # Magic methods\n # Check if has return type\n method_pattern = rf\"public\\s+function\\s+{method}\\s*\\([^)]*\\)\\s*:\\s*\\S+\"\n if not re.search(method_pattern, content):\n block(f\"Public method '{method}' missing return type\")\n```\n\n**Begründung:**\n- Public API muss typisiert sein\n- Aufrufer brauchen Contract\n- IDE-Support und statische Analyse\n\n---\n\n### P3.5: Public Methods müssen typisierte Parameter haben\n\n**Regel:** Jeder Parameter einer public function muss typisiert sein.\n\n**Prüfung:**\n```python\n# Findet untypisierte Parameter: $param ohne Type davor\nuntyped_params = re.findall(\n r\"public\\s+function\\s+\\w+\\s*\\([^)]*(?<![?\\w])\\$\\w+[^)]*\\)\",\n content\n)\n```\n\n**Komplexität:** Regex für Parameter-Parsing ist fehleranfällig.\n\n**Alternative Prüfung (einfacher):**\n```python\n# Suche nach Parametern ohne Type-Hint\n# Pattern: ( oder , gefolgt von $name ohne Type davor\nuntyped = re.findall(r\"[(,]\\s*\\$\\w+\", content)\n```\n\n**Problem:** Das matcht auch typisierte Parameter falsch.\n\n**Entscheidung:** Diese Prüfung ist ohne AST nicht zuverlässig.\n\n**YAGNI-Korrektur:** Streichen oder vereinfachen auf:\n- PHPStan Level 6+ als externe Prüfung\n- Hier nur: Existenz von Type-Hints prüfen (heuristisch)\n\n**Vereinfachte Regel:**\n```python\n# Warnung wenn viele untypisierte Parameter erkannt werden\n# Keine Blockade, da False Positives möglich\n```\n\n**Entscheidung:** Zu Post-Hook verschieben als Warnung, nicht Block.\n\n---\n\n## Post-Hook (WARN)\n\n### W3.1: Untypisierte Parameter (heuristisch)\n\n**Regel:** Warnung wenn Parameter ohne Type-Hint erkannt werden.\n\n**Pattern:**\n```python\n# Heuristik: Suche nach $param direkt nach ( oder ,\n# ohne Type davor\npotential_untyped = re.findall(r\"function\\s+\\w+\\s*\\([^)]*[(,]\\s*\\$\", content)\nif potential_untyped:\n warn(\"Potential untyped parameters detected. Run PHPStan for verification.\")\n```\n\n**Warum Warnung:**\n- Regex kann Type-Hints nicht zuverlässig erkennen\n- False Positives möglich\n- PHPStan ist das richtige Tool dafür\n\n---\n\n### W3.2: Nullable ohne expliziten Null-Check\n\n**Regel:** Warnung wenn nullable Types ohne Null-Handling verwendet werden.\n\n**Problem:** Ohne AST nicht prüfbar.\n\n**Entscheidung:** Streichen. PHPStan macht das besser.\n\n---\n\n### W3.3: Mixed Type verwendet\n\n**Regel:** Warnung bei Verwendung von `mixed` Type.\n\n**Pattern:**\n```python\nif re.search(r\":\\s*mixed\\b\", content):\n warn(\"'mixed' type used. Consider more specific type.\")\n```\n\n**Begründung:**\n- `mixed` ist Type-Escape\n- Sollte vermieden werden\n- Aber: Manchmal legitim (z.B. JSON-Handling)\n\n---\n\n## Gestrichen\n\n### Parameter-Typ-Prüfung als Block\n\n**Ursprüngliche Idee:** Blockieren wenn Parameter ohne Type.\n\n**Warum gestrichen:**\n- Regex-Parsing von PHP-Parametern ist fehleranfällig\n- Default-Values, Variadic, Nullable machen Pattern komplex\n- PHPStan ist das richtige Tool\n- YAGNI: Nicht duplizieren was PHPStan besser kann\n\n**Alternative:** PHPStan als Quality-Gate (externes Tool, nicht Hook)\n\n### Nullable-Handling-Prüfung\n\n**Warum gestrichen:**\n- Kontrollfluss-Analyse ohne AST unmöglich\n- PHPStan macht das\n\n---\n\n## Zusammenfassung\n\n### Pre-Hook\n\n| ID | Prüfung | Aktion |\n|----|---------|--------|\n| P3.1 | strict_types fehlt | BLOCK |\n| P3.2 | Namespace ≠ Pfad | BLOCK |\n| P3.3 | Klassenname ≠ Dateiname | BLOCK |\n| P3.4 | Public Method ohne Return-Type | BLOCK |\n\n### Post-Hook\n\n| ID | Prüfung | Aktion |\n|----|---------|--------|\n| W3.1 | Potentiell untypisierte Parameter | WARN |\n| W3.3 | `mixed` Type verwendet | WARN |\n\n### Allowlist\n\n| Pfad | Grund |\n|------|-------|\n| \/vendor\/ | Third-party |\n| \/tests\/ | Test-Code darf lockerer sein |\n| \/Test\/ | PHPUnit |\n\n---\n\n## Selbstkritik und Entscheidungen\n\n| Frage | Entscheidung | Begründung |\n|-------|--------------|------------|\n| Warum nicht alle Parameter prüfen? | Zu komplex für Regex | PHPStan ist besser, YAGNI |\n| Warum strict_types als Block? | Absolut deterministisch | Entweder da oder nicht |\n| Warum mixed nur Warnung? | Manchmal legitim | JSON, externe APIs |\n| Fehlt Property-Type-Prüfung? | Ja, bewusst | Regex kann Visibility nicht sicher parsen |\n\n---\n\n## Abgrenzung zu externen Tools\n\n| Prüfung | Hook | PHPStan |\n|---------|------|---------|\n| strict_types | ✓ | ✓ |\n| Namespace = Pfad | ✓ | ✓ |\n| Return Types | ✓ | ✓ |\n| Parameter Types | ~ (Warnung) | ✓ (exakt) |\n| Null-Safety | ✗ | ✓ |\n| Type Inference | ✗ | ✓ |\n\n**Fazit:** Hooks prüfen das Offensichtliche. PHPStan prüft das Komplexe.\n\n---\n\n# Prüfung 4-6\n\n*Werden nach Abnahme von Prüfung 3 definiert.*",
"originalFile": "# Code Quality Gates\n\n## Prüfungsreihenfolge\n\n| # | Prinzip | Begründung der Reihenfolge |\n|---|---------|---------------------------|\n| 1 | SRP + KISS | Grundlage für alles. Ohne klare Verantwortung ist nichts anderes prüfbar. |\n| 2 | MVC + CRUD | Strukturelle Trennung. Setzt SRP voraus. |\n| 3 | PSR + Types | Typdisziplin. Muss VOR DIP kommen, weil Interfaces ohne Typen wirkungslos sind. |\n| 4 | OOP | Verhalten + Zustand. Setzt Typen voraus. |\n| 5 | DRY | Duplikationserkennung. Setzt funktionierende Struktur voraus. |\n| 6 | SOLID + DIP | Abhängigkeitsrichtung. Setzt Typen und Interfaces voraus. Kommt zuletzt. |\n\n---\n\n# Prüfung 1: SRP + KISS\n\n## Ziel\n\n**SRP:** Eine Klasse hat genau einen Grund sich zu ändern.\n\n**KISS:** Einfachste Lösung die funktioniert. Keine vorzeitigen Abstraktionen.\n\n## Grundsatz\n\nSRP ist semantisch. Maschinen können \"Verantwortung\" nicht verstehen.\n\nDaher:\n- **Pre-Hook:** Erzwingt explizite Deklaration + blockiert eindeutige Symptome\n- **Post-Hook:** Warnt bei statistischen Indikatoren\n\n---\n\n## Pre-Hook (BLOCK)\n\n### P1.1: Verantwortungs-Header\n\n**Regel:** Jede PHP-Datei muss ihre Verantwortung explizit deklarieren.\n\n**Format:**\n```php\n<?php\ndeclare(strict_types=1);\n\n\/\/ @responsibility: <genau-eine-verantwortung>\n```\n\n**Validierung:**\n\n| Check | Begründung |\n|-------|------------|\n| Header vorhanden | Ohne Deklaration entsteht Scope Creep |\n| Nicht leer | Leerer Header ist wertlos |\n| Keine Multi-Responsibility-Wörter | \"und\", \"sowie\", \"außerdem\", \"zusätzlich\" im Header = mehrere Verantwortungen |\n\n**Pattern:**\n```python\nheader_pattern = r\"\/\/\\s*@responsibility:\\s*(.+)\"\nmulti_words = r\"\\b(und|sowie|außerdem|also|zusätzlich)\\b\"\n\nmatch = re.search(header_pattern, content)\nif not match:\n block(\"Missing @responsibility header\")\n\nresponsibility_text = match.group(1)\nif re.search(multi_words, responsibility_text, re.IGNORECASE):\n block(\"@responsibility contains multi-responsibility indicator\")\n```\n\n**Warum diese Wörter:**\n- \"Validiert Benutzer und erstellt Sessions\" = 2 Verantwortungen\n- \"Lädt Daten sowie formatiert Ausgabe\" = 2 Verantwortungen\n- Erzwingt Nachdenken über Singular-Verantwortung\n\n**Allowlist:** `\/vendor\/`, `\/tests\/`, `\/Test\/`\n\n---\n\n### P1.2: Müllhalden-Namen\n\n**Regel:** Bestimmte Begriffe im Dateinamen sind immer Symptome für fehlende klare Verantwortung.\n\n**Blockiert (case-insensitive):**\n\n| Begriff | Begründung |\n|---------|------------|\n| Helper | \"Hilft bei allem\" = keine klare Verantwortung |\n| Utils | Sammelbecken für Funktionen ohne Heimat |\n| Common | \"Wird überall gebraucht\" = gehört nirgends hin |\n| Misc | Explizit: \"Verschiedenes\" |\n| Base | Oft Gott-Klasse mit Shared-State |\n\n**Nicht blockiert:**\n- \"Manager\" → Kann legitim sein (TransactionManager, EntityManager)\n- \"And\" im Namen → Sprachlich, nicht strukturell\n\n**Pattern:**\n```python\nforbidden = [\"helper\", \"utils\", \"common\", \"misc\", \"base\"]\nfilename_lower = filename.lower()\nfor term in forbidden:\n if term in filename_lower:\n block(f\"Forbidden name: '{term}' indicates unclear responsibility\")\n```\n\n**Keine Ausnahmen.** Diese Begriffe sind niemals architektonisch sauber.\n\n---\n\n## Post-Hook (WARN)\n\n### W1.1: Klassengröße (LOC)\n\n**Regel:** Große Klassen haben oft mehrere Verantwortungen.\n\n| LOC | Aktion |\n|-----|--------|\n| > 200 | Hinweis |\n| > 300 | Warnung |\n\n**Warum zwei Schwellen:**\n- 200: \"Du näherst dich dem Limit\"\n- 300: \"Hier stimmt strukturell etwas nicht\"\n\n**Messung:** Nicht-leere Zeilen.\n\n---\n\n### W1.2: Public-Method-Count\n\n**Regel:** Viele öffentliche Methoden = viele Interaktionspunkte = breite Verantwortung.\n\n| Count | Aktion |\n|-------|--------|\n| > 10 | Warnung |\n\n**Warum 10:**\n- Ein fokussiertes Interface hat 3-7 Methoden\n- 10+ deutet auf Feature Creep\n\n---\n\n### W1.3: Constructor-Parameter\n\n**Regel:** Viele Dependencies = viele Gründe sich zu ändern.\n\n| Count | Aktion |\n|-------|--------|\n| > 5 | Warnung |\n\n**Warum 5:**\n- 1-3: Normal\n- 4-5: Grenzwertig\n- 6+: Fast immer strukturelles Problem\n\n---\n\n### W1.4: Dependency-Count (use-Statements)\n\n**Regel:** Viele Imports = Kopplung an viele Module = viele Änderungsgründe.\n\n| Count | Aktion |\n|-------|--------|\n| > 10 | Warnung |\n\n**Warum 10:**\n- PHP-interne Classes zählen mit\n- Framework-Imports zählen mit\n- 10+ externe Abhängigkeiten ist ein Warnsignal\n\n---\n\n### W1.5: Verdächtige Namen\n\n**Regel:** Manche Begriffe sind nicht eindeutig Müll, aber verdächtig.\n\n| Begriff | Aktion |\n|---------|--------|\n| Manager | Hinweis |\n| And (als Wort) | Hinweis |\n\n**Warum Hinweis statt Block:**\n- \"UserManager\" kann legitim sein\n- \"ReadAndWrite\" kann legitim sein (obwohl fragwürdig)\n- Menschliche Entscheidung erforderlich\n\n---\n\n## Gestrichen\n\n### Verschachtelungstiefe\n\n**Ursprüngliche Idee:** > 4 Ebenen = Warnung\n\n**Warum gestrichen:**\n- Indent-basierte Messung ist nicht zuverlässig\n- PHP erlaubt verschiedene Styles (2, 4, Tabs)\n- Fluent Interfaces erzeugen tiefe Indents ohne Kontrollfluss\n- Array-Definitionen erzeugen tiefe Indents\n- False Positives zu hoch\n\n**Alternative:** Erst implementieren wenn AST-Parsing verfügbar.\n\n---\n\n## Zusammenfassung\n\n### Pre-Hook\n\n| ID | Prüfung | Aktion |\n|----|---------|--------|\n| P1.1 | @responsibility fehlt oder Multi-Responsibility | BLOCK |\n| P1.2 | Müllhalden-Name (Helper, Utils, Common, Misc, Base) | BLOCK |\n\n### Post-Hook\n\n| ID | Prüfung | Schwelle | Aktion |\n|----|---------|----------|--------|\n| W1.1 | LOC | > 300 | WARN |\n| W1.2 | Public Methods | > 10 | WARN |\n| W1.3 | Constructor Params | > 5 | WARN |\n| W1.4 | Dependencies (use) | > 10 | WARN |\n| W1.5 | Verdächtige Namen | Manager, And | HINT |\n\n### Allowlist\n\n| Pfad | Grund |\n|------|-------|\n| \/vendor\/ | Third-party Code |\n| \/tests\/ | Test-Konventionen anders |\n| \/Test\/ | PHPUnit |\n\n---\n\n## Selbstkritik und Entscheidungen\n\n| Frage | Entscheidung | Begründung |\n|-------|--------------|------------|\n| Ist Multi-Word-Check im Header Over-Engineering? | Nein | Ein Regex. Verhindert \"Macht A und B\". Minimaler Aufwand, hoher Nutzen. |\n| Ist Dependency-Count SRP? | Ja | Viele Dependencies = viele Änderungsgründe = SRP-Verletzung per Definition. |\n| Sollen Factories in Allowlist? | Nein | Auch Factories sollten nicht 50 Methoden haben. Wenn zu groß, aufteilen. |\n| Warum keine Kontext-Schwellen für Repositories? | YAGNI | Komplexität ohne klaren Nutzen. Wer > 10 Methoden braucht, soll bewusst entscheiden. |\n\n---\n\n## Implementierung\n\n**Dateien:**\n- `\/var\/www\/tools\/ki-protokoll\/claude-hook\/srp_kiss_guard.py` (Pre-Hook)\n- `\/var\/www\/tools\/ki-protokoll\/claude-hook\/srp_kiss_metrics.py` (Post-Hook)\n\n**Hooks in settings.json:**\n```json\n{\n \"PreToolUse\": [\n {\n \"matcher\": \"Write\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"\/var\/www\/tools\/ki-protokoll\/claude-hook\/srp_kiss_guard.py\",\n \"timeout\": 5\n }\n ]\n }\n ],\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"\/var\/www\/tools\/ki-protokoll\/claude-hook\/srp_kiss_metrics.py\",\n \"timeout\": 5\n }\n ]\n }\n ]\n}\n```\n\n---\n\n# Prüfung 2: MVC + CRUD\n\n## Ziel\n\n**MVC:** Klare Rollentrennung.\n- Controller: Orchestrierung (nimmt Request, ruft UseCase, gibt Response)\n- View: Darstellung (keine Logik)\n- Model: Geschäftslogik (Domain + UseCases)\n\n**CRUD:** Persistenz-Disziplin.\n- Create\/Read\/Update\/Delete gehören in Repository\n- Transaktionen gehören in UseCase oder Repository, nicht Controller\n- Controller orchestriert, persistiert nicht\n\n## Grundsatz\n\nMVC ist strukturell. Die Rollen sind durch Pfade definiert:\n- `\/Controller\/` = Presentation Layer\n- `\/View\/`, `\/templates\/` = Templates\n- `\/Domain\/` = Business Logic\n- `\/UseCases\/`, `\/Application\/` = Application Logic\n- `\/Infrastructure\/Persistence\/` = Repositories\n\n## Abgrenzung zu anderen Prüfungen\n\n| Aspekt | Prüfung | Begründung |\n|--------|---------|------------|\n| Klassen-Größe | 1 (SRP) | Strukturelle Metrik |\n| Methoden-Anzahl | 1 (SRP) | Strukturelle Metrik |\n| Abhängigkeits-Richtung | 6 (DIP) | Dependency Inversion |\n| **Rollen-Einhaltung** | **2 (MVC)** | Controller\/View\/Model Trennung |\n| **Persistenz-Ort** | **2 (CRUD)** | Wer darf DB anfassen |\n\n---\n\n## Pre-Hook (BLOCK)\n\n### P2.1: Keine SQL-Statements in Controller\n\n**Regel:** Controller dürfen keine direkten SQL-Queries enthalten.\n\n**Scope:** `\/Controller\/`\n\n**Pattern:**\n```python\nsql_patterns = [\n r\"\\bSELECT\\s+.+\\s+FROM\\b\",\n r\"\\bINSERT\\s+INTO\\b\",\n r\"\\bUPDATE\\s+\\w+\\s+SET\\b\",\n r\"\\bDELETE\\s+FROM\\b\",\n]\n```\n\n**Warum diese Pattern:**\n- Spezifisch genug um False Positives zu vermeiden\n- `SELECT your country` matcht nicht (kein FROM)\n- `INSERT INTO users` matcht (eindeutig SQL)\n\n**Unterschied zu Prüfung 1\/6:**\n- H-Regeln prüfen Objekte (new Repository, DatabaseFactory)\n- P2.1 prüft String-Literale (SQL-Statements)\n\n---\n\n### P2.2: Keine Transaktionen in Controller\n\n**Regel:** Transaktions-Kontrolle gehört nicht in Controller.\n\n**Scope:** `\/Controller\/`\n\n**Pattern:**\n```python\ntransaction_patterns = [\n r\"\\bbeginTransaction\\s*\\(\",\n r\"\\bcommit\\s*\\(\",\n r\"\\brollBack\\s*\\(\",\n r\"\\brollback\\s*\\(\",\n]\n```\n\n**Begründung:**\n- Transaktionen sind Infrastruktur-Concern\n- Controller soll Request→Response orchestrieren, nicht DB-Konsistenz managen\n- Transaktionen gehören in UseCase oder Repository\n\n---\n\n### P2.3: Kein direktes Rendering in Controller\n\n**Regel:** Controller erzeugen keine direkte Ausgabe.\n\n**Scope:** `\/Controller\/`\n\n**Pattern:**\n```python\noutput_patterns = [\n r\"\\becho\\s\",\n r\"\\becho\\(\",\n r\"\\bprint\\s\",\n r\"\\bprint\\(\",\n]\n```\n\n**Nicht blockiert:**\n- `print_r` (Debug)\n- `printf` (Formatierung, selten)\n- `return json_encode()` (Response-Objekt)\n\n**Begründung:**\n- Output gehört in View oder Response-Objekt\n- echo\/print in Controller = MVC-Verletzung\n- CLI-Code gehört in `\/Console\/` oder `\/Command\/`, nicht `\/Controller\/`\n\n---\n\n### P2.4: Keine DB-Artefakte in UseCases\n\n**Regel:** UseCases nutzen Repository-Interfaces, nicht direkte DB.\n\n**Scope:** `\/UseC... [TRUNCATED-f729f390425a4edc]",
"structuredPatch": [
{
"oldStart": 538,
"oldLines": 6,
"newStart": 538,
"newLines": 332,
"lines": [
" ",
" ---",
" ",
"-# Prüfung 3-6",
"+# Prüfung 3: PSR + Types",
" ",
"-*Werden nach Abnahme von Prüfung 2 definiert.*",
"+## Ziel",
"+",
"+**PSR (PHP Standards Recommendations):**",
"+- PSR-1: Basic Coding Standard",
"+- PSR-4: Autoloading (Namespace = Pfad)",
"+- PSR-12: Extended Coding Style",
"+",
"+**Types (Typdisziplin):**",
"+- `declare(strict_types=1)` in jeder Datei",
"+- Typ-Deklarationen für Parameter",
"+- Return-Type-Deklarationen",
"+- Property-Types (PHP 7.4+)",
"+",
"+## Warum vor SOLID\/DIP?",
"+",
"+DIP (Dependency Inversion) basiert auf Interfaces.",
"+Interfaces ohne Typen sind wirkungslos.",
"+Beispiel:",
"+",
"+```php",
"+\/\/ Ohne Types: Interface ist Dekoration",
"+interface UserRepository {",
"+ public function find($id); \/\/ Was ist $id? Was kommt zurück?",
"+}",
"+",
"+\/\/ Mit Types: Interface ist Contract",
"+interface UserRepository {",
"+ public function find(int $id): ?User;",
"+}",
"+```",
"+",
"+**Types vor DIP ist zwingend.**",
"+",
"+## Grundsatz",
"+",
"+PSR und Types sind vollständig deterministisch prüfbar.",
"+Kein semantischer Spielraum. Kein Kontext erforderlich.",
"+Daher: Alle Regeln sind Pre-Hook (BLOCK).",
"+",
"+---",
"+",
"+## Pre-Hook (BLOCK)",
"+",
"+### P3.1: strict_types erforderlich",
"+",
"+**Regel:** Jede PHP-Datei muss `declare(strict_types=1)` haben.",
"+",
"+**Pattern:**",
"+```python",
"+strict_types_pattern = r\"declare\\s*\\(\\s*strict_types\\s*=\\s*1\\s*\\)\\s*;\"",
"+if not re.search(strict_types_pattern, content):",
"+ block(\"Missing declare(strict_types=1)\")",
"+```",
"+",
"+**Position:** Muss in den ersten 5 Zeilen sein (nach <?php, vor namespace).",
"+",
"+**Begründung:**",
"+- Ohne strict_types ist PHP-Typisierung wirkungslos",
"+- Type Coercion versteckt Bugs",
"+- Grundlage für alle weiteren Type-Prüfungen",
"+",
"+---",
"+",
"+### P3.2: Namespace muss Pfad entsprechen (PSR-4)",
"+",
"+**Regel:** Der Namespace muss dem Dateipfad entsprechen.",
"+",
"+**Mapping:**",
"+```python",
"+path_to_namespace = {",
"+ \"\/src\/Controller\/\": \"Controller\\\\\",",
"+ \"\/src\/Domain\/\": \"Domain\\\\\",",
"+ \"\/src\/UseCases\/\": \"UseCases\\\\\",",
"+ \"\/src\/Application\/\": \"Application\\\\\",",
"+ \"\/src\/Infrastructure\/\": \"Infrastructure\\\\\",",
"+ \"\/src\/Framework\/\": \"Framework\\\\\",",
"+ \"\/app\/\": \"App\\\\\",",
"+}",
"+```",
"+",
"+**Prüfung:**",
"+```python",
"+namespace_match = re.search(r\"namespace\\s+([^;]+);\", content)",
"+if namespace_match:",
"+ declared_namespace = namespace_match.group(1)",
"+ expected_prefix = get_expected_prefix(file_path)",
"+ if not declared_namespace.startswith(expected_prefix):",
"+ block(f\"Namespace '{declared_namespace}' does not match path. Expected: '{expected_prefix}...'\")",
"+```",
"+",
"+**Begründung:**",
"+- PSR-4 Autoloading erfordert Pfad-Namespace-Konsistenz",
"+- Inkonsistenz führt zu Autoload-Fehlern",
"+- IDE-Navigation bricht",
"+",
"+---",
"+",
"+### P3.3: Klassenname muss Dateiname entsprechen (PSR-1)",
"+",
"+**Regel:** Der Klassenname muss dem Dateinamen entsprechen.",
"+",
"+**Prüfung:**",
"+```python",
"+class_match = re.search(r\"(?:class|interface|trait|enum)\\s+(\\w+)\", content)",
"+if class_match:",
"+ class_name = class_match.group(1)",
"+ expected_name = Path(file_path).stem # Filename without .php",
"+ if class_name != expected_name:",
"+ block(f\"Class '{class_name}' does not match filename '{expected_name}'\")",
"+```",
"+",
"+**Begründung:**",
"+- PSR-1 Standard",
"+- Eine Klasse pro Datei",
"+- Dateiname = Klassenname",
"+",
"+---",
"+",
"+### P3.4: Public Methods müssen Return-Type haben",
"+",
"+**Regel:** Jede public function muss einen Return-Type deklarieren.",
"+",
"+**Pattern:**",
"+```python",
"+# Findet public functions ohne Return-Type",
"+public_without_return = re.findall(",
"+ r\"public\\s+function\\s+\\w+\\s*\\([^)]*\\)\\s*(?!:)\\s*\\{\",",
"+ content",
"+)",
"+if public_without_return:",
"+ block(f\"Public method(s) without return type declaration\")",
"+```",
"+",
"+**Ausnahmen:**",
"+- `__construct` (kein Return-Type erlaubt)",
"+- `__destruct` (kein Return-Type erlaubt)",
"+",
"+**Verfeinertes Pattern:**",
"+```python",
"+public_methods = re.findall(",
"+ r\"public\\s+function\\s+(\\w+)\\s*\\([^)]*\\)\\s*(?::\\s*\\S+)?\\s*\\{\",",
"+ content",
"+)",
"+for method in public_methods:",
"+ if method.startswith(\"__\"):",
"+ continue # Magic methods",
"+ # Check if has return type",
"+ method_pattern = rf\"public\\s+function\\s+{method}\\s*\\([^)]*\\)\\s*:\\s*\\S+\"",
"+ if not re.search(method_pattern, content):",
"+ block(f\"Public method '{method}' missing return type\")",
"+```",
"+",
"+**Begründung:**",
"+- Public API muss typisiert sein",
"+- Aufrufer brauchen Contract",
"+- IDE-Support und statische Analyse",
"+",
"+---",
"+",
"+### P3.5: Public Methods müssen typisierte Parameter haben",
"+",
"+**Regel:** Jeder Parameter einer public function muss typisiert sein.",
"+",
"+**Prüfung:**",
"+```python",
"+# Findet untypisierte Parameter: $param ohne Type davor",
"+untyped_params = re.findall(",
"+ r\"public\\s+function\\s+\\w+\\s*\\([^)]*(?<![?\\w])\\$\\w+[^)]*\\)\",",
"+ content",
"+)",
"+```",
"+",
"+**Komplexität:** Regex für Parameter-Parsing ist fehleranfällig.",
"+",
"+**Alternative Prüfung (einfacher):**",
"+```python",
"+# Suche nach Parametern ohne Type-Hint",
"+# Pattern: ( oder , gefolgt von $name ohne Type davor",
"+untyped = re.findall(r\"[(,]\\s*\\$\\w+\", content)",
"+```",
"+",
"+**Problem:** Das matcht auch typisierte Parameter falsch.",
"+",
"+**Entscheidung:** Diese Prüfung ist ohne AST nicht zuverlässig.",
"+",
"+**YAGNI-Korrektur:** Streichen oder vereinfachen auf:",
"+- PHPStan Level 6+ als externe Prüfung",
"+- Hier nur: Existenz von Type-Hints prüfen (heuristisch)",
"+",
"+**Vereinfachte Regel:**",
"+```python",
"+# Warnung wenn viele untypisierte Parameter erkannt werden",
"+# Keine Blockade, da False Positives möglich",
"+```",
"+",
"+**Entscheidung:** Zu Post-Hook verschieben als Warnung, nicht Block.",
"+",
"+---",
"+",
"+## Post-Hook (WARN)",
"+",
"+### W3.1: Untypisierte Parameter (heuristisch)",
"+",
"+**Regel:** Warnung wenn Parameter ohne Type-Hint erkannt werden.",
"+",
"+**Pattern:**",
"+```python",
"+# Heuristik: Suche nach $param direkt nach ( oder ,",
"+# ohne Type davor",
"+potential_untyped = re.findall(r\"function\\s+\\w+\\s*\\([^)]*[(,]\\s*\\$\", content)",
"+if potential_untyped:",
"+ warn(\"Potential untyped parameters detected. Run PHPStan for verification.\")",
"+```",
"+",
"+**Warum Warnung:**",
"+- Regex kann Type-Hints nicht zuverlässig erkennen",
"+- False Positives möglich",
"+- PHPStan ist das richtige Tool dafür",
"+",
"+---",
"+",
"+### W3.2: Nullable ohne expliziten Null-Check",
"+",
"+**Regel:** Warnung wenn nullable Types ohne Null-Handling verwendet werden.",
"+",
"+**Problem:** Ohne AST nicht prüfbar.",
"+",
"+**Entscheidung:** Streichen. PHPStan macht das besser.",
"+",
"+---",
"+",
"+### W3.3: Mixed Type verwendet",
"+",
"+**Regel:** Warnung bei Verwendung von `mixed` Type.",
"+",
"+**Pattern:**",
"+```python",
"+if re.search(r\":\\s*mixed\\b\", content):",
"+ warn(\"'mixed' type used. Consider more specific type.\")",
"+```",
"+",
"+**Begründung:**",
"+- `mixed` ist Type-Escape",
"+- Sollte vermieden werden",
"+- Aber: Manchmal legitim (z.B. JSON-Handling)",
"+",
"+---",
"+",
"+## Gestrichen",
"+",
"+### Parameter-Typ-Prüfung als Block",
"+",
"+**Ursprüngliche Idee:** Blockieren wenn Parameter ohne Type.",
"+",
"+**Warum gestrichen:**",
"+- Regex-Parsing von PHP-Parametern ist fehleranfällig",
"+- Default-Values, Variadic, Nullable machen Pattern komplex",
"+- PHPStan ist das richtige Tool",
"+- YAGNI: Nicht duplizieren was PHPStan besser kann",
"+",
"+**Alternative:** PHPStan als Quality-Gate (externes Tool, nicht Hook)",
"+",
"+### Nullable-Handling-Prüfung",
"+",
"+**Warum gestrichen:**",
"+- Kontrollfluss-Analyse ohne AST unmöglich",
"+- PHPStan macht das",
"+",
"+---",
"+",
"+## Zusammenfassung",
"+",
"+### Pre-Hook",
"+",
"+| ID | Prüfung | Aktion |",
"+|----|---------|--------|",
"+| P3.1 | strict_types fehlt | BLOCK |",
"+| P3.2 | Namespace ≠ Pfad | BLOCK |",
"+| P3.3 | Klassenname ≠ Dateiname | BLOCK |",
"+| P3.4 | Public Method ohne Return-Type | BLOCK |",
"+",
"+### Post-Hook",
"+",
"+| ID | Prüfung | Aktion |",
"+|----|---------|--------|",
"+| W3.1 | Potentiell untypisierte Parameter | WARN |",
"+| W3.3 | `mixed` Type verwendet | WARN |",
"+",
"+### Allowlist",
"+",
"+| Pfad | Grund |",
"+|------|-------|",
"+| \/vendor\/ | Third-party |",
"+| \/tests\/ | Test-Code darf lockerer sein |",
"+| \/Test\/ | PHPUnit |",
"+",
"+---",
"+",
"+## Selbstkritik und Entscheidungen",
"+",
"+| Frage | Entscheidung | Begründung |",
"+|-------|--------------|------------|",
"+| Warum nicht alle Parameter prüfen? | Zu komplex für Regex | PHPStan ist besser, YAGNI |",
"+| Warum strict_types als Block? | Absolut deterministisch | Entweder da oder nicht |",
"+| Warum mixed nur Warnung? | Manchmal legitim | JSON, externe APIs |",
"+| Fehlt Property-Type-Prüfung? | Ja, bewusst | Regex kann Visibility nicht sicher parsen |",
"+",
"+---",
"+",
"+## Abgrenzung zu externen Tools",
"+",
"+| Prüfung | Hook | PHPStan |",
"+|---------|------|---------|",
"+| strict_types | ✓ | ✓ |",
"+| Namespace = Pfad | ✓ | ✓ |",
"+| Return Types | ✓ | ✓ |",
"+| Parameter Types | ~ (Warnung) | ✓ (exakt) |",
"+| Null-Safety | ✗ | ✓ |",
"+| Type Inference | ✗ | ✓ |",
"+",
"+**Fazit:** Hooks prüfen das Offensichtliche. PHPStan prüft das Komplexe.",
"+",
"+---",
"+",
"+# Prüfung 4-6",
"+",
"+*Werden nach Abnahme von Prüfung 3 definiert.*"
]
}
],
"userModified": false,
"replaceAll": false
}
}