feat(06-admin-base): Admin\ base infrastructure — Form Edit System + Support layer (Phase 6)

Phase 6 zamknięta po 2 planach. Pełny fundament dla Phase 7-13 (migracja
17 admin controllers do Admin\ namespace).

06-01 (Forms infrastructure):
- Admin\ViewModels\Forms\* — 5 ViewModeli (687 L)
- Admin\Validation\FormValidator (196 L)
- composer.json: php >=7.4, PSR-4 paths cross-platform safe
  (Admin\ → autoload/admin/, Frontend\ → autoload/front/)

06-02 (Support layer):
- Admin\Support\TableListRequestFactory (99 L) — parser list z $_GET
- Admin\Support\Forms\FormRequestHandler (159 L) — POST + CSRF + walidacja + persist
- Admin\Support\Forms\FormFieldRenderer (494 L) — renderer HTML pól

Decyzje:
- Brak BaseController — Phase 7+ kontrolery jako POJOs z DI (jak shopPRO)
- PSR-4 filename fix: TableListRequestFactory.php (bez shopPRO 'class.' prefix)
- PascalCase namespace (Admin\Support) na lowercase folder admin/
  ze względu na Windows fs case-insensitivity vs legacy admin/controls/

Pliki: 8 nowych klas, 1635 L kodu PHP 7.4-kompatybilnego, zero regresji.
Smoke test: walidacja e-maila zwraca PL komunikat, factory parsuje
?page=&per_page=&sort=&filter=, Domain/Shared nadal ładują się.

PHPUnit: 37/37 OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 23:32:26 +02:00
parent 72cb5b8d1d
commit a3caeb9a9a
25 changed files with 2694 additions and 29 deletions

View File

@@ -0,0 +1,266 @@
---
phase: 06-admin-base
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- autoload/Admin/ViewModels/Forms/FormEditViewModel.php
- autoload/Admin/ViewModels/Forms/FormField.php
- autoload/Admin/ViewModels/Forms/FormFieldType.php
- autoload/Admin/ViewModels/Forms/FormTab.php
- autoload/Admin/ViewModels/Forms/FormAction.php
- autoload/Admin/Validation/FormValidator.php
- composer.json
autonomous: true
delegation: off
---
<objective>
## Goal
Założyć fundament `Admin\` namespace przez przeniesienie warstwy formularzy z shopPRO: 5 ViewModeli (FormEditViewModel, FormField, FormFieldType, FormTab, FormAction) w `Admin\ViewModels\Forms\` oraz `FormValidator` w `Admin\Validation\`. Plan kończy się działającym, autoloadowanym systemem definicji + walidacji formularzy gotowym do użycia w Phase 7+.
## Purpose
ROADMAP Phase 6 (Admin: Base Infrastructure) wymaga bazowych klas Admin\ — Form Edit System jest pierwszym filarem (kontrolery z Phase 7-13 będą zwracać `FormEditViewModel` zamiast budować HTML inline). PROJECT.md deklarował to ukończone, ale audyt wykazał brak — Phase 6/Plan 01 to nadrabia, kopiując stabilny wzorzec z shopPRO.
## Output
- `autoload/Admin/ViewModels/Forms/` — 5 plików (FormEditViewModel, FormField, FormFieldType, FormTab, FormAction)
- `autoload/Admin/Validation/FormValidator.php`
- `composer.json` z `"php": ">=7.4"`
- `.paul/phases/06-admin-base/06-01-SUMMARY.md`
</objective>
<context>
<clarifications>
- **Form VMs** — Czy mamy już Form ViewModele, czy kopiować z shopPRO?
→ Odpowiedź: Skopiuj z shopPRO teraz (PROJECT.md deklarował, ale plików nie ma).
- **NS case** — Jaka konwencja namespace dla Admin\?
→ Odpowiedź: Fully PascalCase (`Admin\Base`, `Admin\Support`, `Admin\ViewModels\Forms`, `Admin\Validation`). Nie powielać błędu shopPRO (`admin\` lowercase).
- **PHP target** — Cel PHP dla nowego kodu?
→ Odpowiedź: `>=7.4` w composer.json. Docelowo musi działać też na 8.4/8.5+, więc kod ma być forward-compatible (typed properties OK, ale unikać deprecated wzorców 8.x).
- **Scope** — Zakres Phase 6?
→ Odpowiedź: Splituję Phase 6 na 2 plany. 06-01 = Forms (ten plan). 06-02 = TableListRequestFactory + BaseController.
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/codebase/architecture.md
## Reference Source (shopPRO — wzorzec do skopiowania)
@C:\visual studio code\projekty\shopPRO\autoload\Admin\ViewModels\Forms\FormEditViewModel.php
@C:\visual studio code\projekty\shopPRO\autoload\Admin\ViewModels\Forms\FormField.php
@C:\visual studio code\projekty\shopPRO\autoload\Admin\ViewModels\Forms\FormFieldType.php
@C:\visual studio code\projekty\shopPRO\autoload\Admin\ViewModels\Forms\FormTab.php
@C:\visual studio code\projekty\shopPRO\autoload\Admin\ViewModels\Forms\FormAction.php
@C:\visual studio code\projekty\shopPRO\autoload\Admin\Validation\FormValidator.php
## cmsPRO target paths
- `autoload/Admin/ViewModels/Forms/`
- `autoload/Admin/Validation/`
## Composer
@composer.json
</context>
<acceptance_criteria>
## AC-1: Form ViewModels load via PSR-4
```gherkin
Given composer.json mapuje "Admin\\" "autoload/Admin/"
When wykonam `composer dump-autoload && php -r "var_dump(class_exists('Admin\\ViewModels\\Forms\\FormEditViewModel'));"`
Then otrzymam `bool(true)` dla wszystkich 5 klas (FormEditViewModel, FormField, FormFieldType, FormTab, FormAction)
```
## AC-2: FormValidator namespace and dependency
```gherkin
Given FormValidator zawiera `use Admin\ViewModels\Forms\FormField; use Admin\ViewModels\Forms\FormFieldType;`
When `php -r "var_dump(class_exists('Admin\\Validation\\FormValidator'));"`
Then otrzymam `bool(true)` i nie wystąpi fatal error o brakujących klasach
```
## AC-3: Smoke test — utworzenie i walidacja formularza
```gherkin
Given załadowane klasy z AC-1 i AC-2
When utworzę `FormField('email', FormFieldType::EMAIL, 'E-mail', '', 'main', true)` + uruchomię `FormValidator::validate(['email' => 'not-an-email'], [$field])`
Then otrzymam tablicę `['email' => '...musi być poprawnym adresem e-mail.']` (lub równoważny komunikat) walidacja działa
```
## AC-4: PHP version constraint i konwencja PascalCase
```gherkin
Given composer.json
When odczytam pole `require.php`
Then jest równe ">=7.4"
And wszystkie nowe pliki mają `namespace Admin\ViewModels\Forms;` lub `namespace Admin\Validation;` (PascalCase pierwszy segment, w przeciwieństwie do shopPRO `admin\`)
```
## AC-5: Zero regresji na istniejącym kodzie
```gherkin
Given commit przed planem
When wykonam `php -l` na każdym zmodyfikowanym pliku oraz `php -r "require 'autoload/autoloader.php';"` (lub equivalent inicjalizacji projektu)
Then żadnych błędów składni i żadnych warning/notice o redefinicji klas; istniejące Domain repos (np. `Domain\Articles\ArticlesRepository`) nadal się ładują
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Skopiuj 5 Form ViewModeli z shopPRO do Admin\ViewModels\Forms\ z PascalCase namespace</name>
<files>autoload/Admin/ViewModels/Forms/FormEditViewModel.php, autoload/Admin/ViewModels/Forms/FormField.php, autoload/Admin/ViewModels/Forms/FormFieldType.php, autoload/Admin/ViewModels/Forms/FormTab.php, autoload/Admin/ViewModels/Forms/FormAction.php</files>
<action>
Utwórz katalog `autoload/Admin/ViewModels/Forms/` (PascalCase — jeśli system plików ma kolizję z istniejącym `autoload/admin/`, NIE łącz ich; `Admin/` to nowy namespace PSR-4, `admin/` to legacy lowercase i pozostaje nietknięty).
Skopiuj 5 plików ze shopPRO 1:1 z TYLKO tymi modyfikacjami:
1. **Zmień `namespace admin\ViewModels\Forms;` → `namespace Admin\ViewModels\Forms;`** (każdy plik, pierwsza linia po `<?php`)
2. Wewnętrzne `use` (jeśli są) — zaktualizuj wszelkie odwołania `admin\ViewModels\Forms\X``Admin\ViewModels\Forms\X`
3. **NIE zmieniaj** sygnatur, typed properties, JSDoców, defaultów, statycznych helperów (np. `FormAction::save()`)
Pliki do skopiowania (źródło → cel):
- `C:\visual studio code\projekty\shopPRO\autoload\Admin\ViewModels\Forms\FormEditViewModel.php``autoload/Admin/ViewModels/Forms/FormEditViewModel.php`
- `...\FormField.php``autoload/Admin/ViewModels/Forms/FormField.php`
- `...\FormFieldType.php``autoload/Admin/ViewModels/Forms/FormFieldType.php`
- `...\FormTab.php``autoload/Admin/ViewModels/Forms/FormTab.php`
- `...\FormAction.php``autoload/Admin/ViewModels/Forms/FormAction.php`
Avoid:
- Wymienianie `public string $name;` na `string $name` (PHP 7.4 wspiera typed properties, jest OK)
- Dodawanie nowych funkcji „przy okazji"
- Mieszania case'a w nazwach katalogów (musi być dokładnie `Admin/ViewModels/Forms/`)
</action>
<verify>
`php -l autoload/Admin/ViewModels/Forms/FormEditViewModel.php` (i pozostałe 4) → "No syntax errors detected"
`grep -c "^namespace Admin\\\\ViewModels\\\\Forms;" autoload/Admin/ViewModels/Forms/*.php` → 5
`grep -c "^namespace admin\\\\" autoload/Admin/ViewModels/Forms/*.php` → 0
</verify>
<done>AC-1 częściowo (klasy istnieją), AC-4 częściowo (PascalCase namespace).</done>
</task>
<task type="auto">
<name>Task 2: Skopiuj FormValidator do Admin\Validation\ z PascalCase namespace i poprawnymi `use`</name>
<files>autoload/Admin/Validation/FormValidator.php</files>
<action>
Utwórz katalog `autoload/Admin/Validation/` i skopiuj `FormValidator.php` ze shopPRO z modyfikacjami:
1. `namespace admin\Validation;``namespace Admin\Validation;`
2. `use admin\ViewModels\Forms\FormField;``use Admin\ViewModels\Forms\FormField;`
3. `use admin\ViewModels\Forms\FormFieldType;``use Admin\ViewModels\Forms\FormFieldType;`
4. Cała reszta (logika walidacji, metody prywatne `validateField`, `validateLangSection`, `isEmpty`) bez zmian.
Avoid:
- Refaktoryzacji walidatora przy okazji (nawet jeśli widzisz coś do poprawy — zapisz w SUMMARY jako deferred issue)
- Zmiany komunikatów błędów (są używane w UI 1:1)
</action>
<verify>
`php -l autoload/Admin/Validation/FormValidator.php` → "No syntax errors detected"
`grep -E "^namespace |^use " autoload/Admin/Validation/FormValidator.php` zwraca:
`namespace Admin\Validation;`
`use Admin\ViewModels\Forms\FormField;`
`use Admin\ViewModels\Forms\FormFieldType;`
</verify>
<done>AC-2 satisfied: FormValidator ładuje się i zna FormField/FormFieldType.</done>
</task>
<task type="auto">
<name>Task 3: Dodaj `php: >=7.4` do composer.json i zregeneruj autoloader</name>
<files>composer.json</files>
<action>
W `composer.json` dodaj sekcję `require` z `"php": ">=7.4"` PRZED `require-dev`. Jeśli sekcja `require` już istnieje — dopisz tylko klucz `php`. Zachowaj istniejące mapowania PSR-4 (`Domain\\`, `Shared\\`, `Admin\\`, `Frontend\\`).
Po edycji uruchom `composer dump-autoload` (lub `php composer.phar dump-autoload` jeśli composer nie jest globalny).
Avoid:
- Aktualizacji composer.lock przez `composer update`
- Dodawania nowych dependencies
- Usuwania `Frontend\\` mapowania (nieużywane teraz, ale zostaje dla Phase 14+)
</action>
<verify>
`php -r "echo json_decode(file_get_contents('composer.json'), true)['require']['php'] ?? 'MISSING';"` → ">=7.4"
`php -r "require 'vendor/autoload.php'; var_dump(class_exists('Admin\\\\ViewModels\\\\Forms\\\\FormEditViewModel'));"``bool(true)`
`php -r "require 'vendor/autoload.php'; var_dump(class_exists('Admin\\\\Validation\\\\FormValidator'));"``bool(true)`
</verify>
<done>AC-1 satisfied (autoload działa dla wszystkich 6 klas), AC-4 satisfied (php>=7.4).</done>
</task>
<task type="auto">
<name>Task 4: Smoke test — runtime sanity check formularza</name>
<files>(brak modyfikacji — tylko weryfikacja runtime)</files>
<action>
Uruchom inline PHP smoke check:
```
php -r "require 'vendor/autoload.php';
use Admin\ViewModels\Forms\FormField;
use Admin\ViewModels\Forms\FormFieldType;
use Admin\Validation\FormValidator;
$field = new FormField('email', FormFieldType::EMAIL, 'E-mail', '', 'main', true);
$v = new FormValidator();
\$errors = \$v->validate(['email' => 'not-an-email'], [\$field]);
echo isset(\$errors['email']) ? 'OK: '.\$errors['email'] : 'FAIL';"
```
(Składnię cudzysłowów dostosuj do shell — bash vs cmd. Cel: dostać linię zaczynającą się od `OK:` z polskim komunikatem walidacji.)
Następnie sprawdź lint na wszystkich nowych plikach:
`for f in autoload/Admin/ViewModels/Forms/*.php autoload/Admin/Validation/*.php; do php -l "$f"; done`
Avoid:
- Tworzenia trwałego pliku testowego — to jednorazowy smoke check, wynik wkleimy do SUMMARY
- Modyfikacji jakichkolwiek plików legacy (admin/, autoload/admin/) — Phase 6 ich nie dotyczy
</action>
<verify>
Output zawiera "OK:" + komunikat o niepoprawnym e-mailu
`php -l` na wszystkich 6 plikach → 6× "No syntax errors detected"
`git status` pokazuje TYLKO: 5 nowych plików w `autoload/Admin/ViewModels/Forms/`, 1 nowy plik w `autoload/Admin/Validation/`, zmodyfikowany `composer.json`, regenerowany `vendor/composer/autoload_*` (jeśli vendor/ jest tracked)
</verify>
<done>AC-3 satisfied (walidacja działa runtime), AC-5 satisfied (zero regresji — tylko nowe pliki + composer.json).</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `autoload/admin/` (legacy, lowercase) — zostaje nietknięte do Phase 19
- `autoload/Domain/**` — Phase 5 closed, repozytoria stabilne
- `autoload/Shared/**` — Phase 2 closed
- `autoload/autoloader.php` — Phase 1 (hybrid loader działa); composer dump zaktualizuje vendor/, autoloader.php nie wymaga edycji
- `composer.lock` — nie aktualizuj (`composer update` zakazany w tym planie)
- shopPRO source files — read-only reference
## SCOPE LIMITS
- TableListRequestFactory → Plan 06-02 (NIE w tym planie)
- BaseController → Plan 06-02
- Admin\App.php (logowanie, special_actions, routing) → Phase 12 (Users + 2FA)
- Refaktoring/poprawki w skopiowanych klasach — zero zmian merytorycznych, tylko namespace
- Migracja istniejących admin\controls\* na nowe Form VMs → Phase 7+ (per moduł)
- Tworzenie templates/partials renderujących `FormEditViewModel` → poza zakresem Phase 6
- PHPUnit testy dla FormValidator → Phase 18
</boundaries>
<verification>
Przed declared complete:
- [ ] `php -l` zielony dla wszystkich 6 nowych plików
- [ ] `composer dump-autoload` zwraca exit 0
- [ ] Smoke test (Task 4) zwraca "OK:..."
- [ ] `grep -r "namespace admin\\\\" autoload/Admin/` → brak wyników (zero lowercase namespace w nowym Admin/)
- [ ] `git diff` pokazuje tylko: nowe pliki Admin/ + composer.json + vendor/composer/autoload_classmap.php (regen)
- [ ] Wszystkie AC (1-5) spełnione
</verification>
<success_criteria>
- 5 Form ViewModeli + FormValidator dostępne pod namespace `Admin\ViewModels\Forms\` i `Admin\Validation\`
- composer.json deklaruje `php: >=7.4`
- Smoke test walidacji e-maila zwraca komunikat błędu po polsku
- Zero zmian w legacy `autoload/admin/`, `Domain/`, `Shared/`
- Plan gotowy do unify → następnie Plan 06-02
</success_criteria>
<output>
Po wykonaniu utworzyć `.paul/phases/06-admin-base/06-01-SUMMARY.md` z:
- Lista skopiowanych plików (z liczbą linii)
- Output smoke testu (literalnie)
- Wszelkie deferred issues (np. coś co warto zrefaktoryzować w FormValidator/FormField w przyszłości)
- Setup dla Plan 06-02 (jakie założenia po 06-01: Admin\ namespace działa, Form VMs dostępne)
</output>