Files
cmsPRO/.paul/phases/06-admin-base/06-02-PLAN.md
Jacek Pyziak a3caeb9a9a 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>
2026-04-30 23:32:26 +02:00

16 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, delegation
phase plan type wave depends_on files_modified autonomous delegation
06-admin-base 02 execute 1
06-01
autoload/admin/Support/TableListRequestFactory.php
autoload/admin/Support/Forms/FormRequestHandler.php
autoload/admin/Support/Forms/FormFieldRenderer.php
true off
## Goal Domknąć Phase 6 (Admin Base Infrastructure) przez przeniesienie z shopPRO 3 klas warstwy Support: `TableListRequestFactory` (parser requestów listy z filtrami/sortowaniem/paginacją), `FormRequestHandler` (obsługa POST/persist/walidacji formularza), `FormFieldRenderer` (renderer HTML pól formularza). Po 06-02 cała infrastruktura Phase 7+ (kontrolery) jest gotowa.

Purpose

Phase 7-13 będą migrować 17 modułów Admin do nowych namespace'ów. Każdy z tych kontrolerów będzie potrzebował:

  • TableListRequestFactory::fromRequest() — uniformowy parsing list z ?page=&per_page=&sort=&filter[]=
  • FormRequestHandler — uniformowy handler POST → walidacja → persist → redirect
  • FormFieldRenderer — generowanie HTML pól z FormField ViewModeli (zastępuje Shared\Html\Html dla nowych formularzy)

Bez tych helperów każdy kontroler musiałby duplikować logikę paginacji i obsługi POST. Pakiet 06-01 + 06-02 = pełny fundament Admin\ gotowy do Phase 7.

Decyzja architektoniczna: Brak BaseController — shopPRO używa POJOs z DI (np. BannerController(BannerRepository, LanguagesRepository)), nie dziedziczenia. Phase 7+ kontrolery będą zwykłymi klasami z Domain repos i tymi helperami w konstruktorze.

Output

  • autoload/admin/Support/TableListRequestFactory.php
  • autoload/admin/Support/Forms/FormRequestHandler.php
  • autoload/admin/Support/Forms/FormFieldRenderer.php
  • .paul/phases/06-admin-base/06-02-SUMMARY.md
- **BaseCtrl** — Czy tworzymy `BaseController`? → Odpowiedź: NIE. POJOs z DI jak shopPRO. ROADMAP zaktualizowany — wpis BaseController usunięty ze scope Phase 6. - **Helpers** — Które helpery z `Admin/Support/Forms/` w 06-02? → Odpowiedź: Oba — `FormRequestHandler` (159 L) + `FormFieldRenderer` (494 L). Pełny pakiet, żeby Phase 7+ miało gotowe wszystko. - **Filename** — Konwencja nazwy pliku? → Odpowiedź: PSR-4 czyste — `TableListRequestFactory.php` (bez `class.` prefiksu z shopPRO). Zgodne z resztą cmsPRO Domain/Shared (np. `ArticlesRepository.php`).

Project Context

@.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md @.paul/codebase/architecture.md

Prior Work

@.paul/phases/06-admin-base/06-01-SUMMARY.md

Reference Source (shopPRO — wzorzec do skopiowania)

@C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\class.TableListRequestFactory.php @C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormRequestHandler.php @C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormFieldRenderer.php

cmsPRO target paths

  • autoload/admin/Support/TableListRequestFactory.php (Admin\Support\TableListRequestFactory)
  • autoload/admin/Support/Forms/FormRequestHandler.php (Admin\Support\Forms\FormRequestHandler)
  • autoload/admin/Support/Forms/FormFieldRenderer.php (Admin\Support\Forms\FormFieldRenderer)

Uwaga: PSR-4 mapping w composer.json (po 06-01) to "Admin\\": "autoload/admin/". Folder na disku jest lowercase admin/ (legacy collision z PascalCase Windows fs), ale namespace pozostaje PascalCase Admin\Support.

<acceptance_criteria>

AC-1: Wszystkie 3 klasy ładują się przez PSR-4

Given composer dump-autoload wykonany po stworzeniu plików
When `php -r "require 'vendor/autoload.php'; var_dump(class_exists('Admin\\Support\\TableListRequestFactory'), class_exists('Admin\\Support\\Forms\\FormRequestHandler'), class_exists('Admin\\Support\\Forms\\FormFieldRenderer'));"`
Then otrzymam `bool(true) bool(true) bool(true)`

AC-2: TableListRequestFactory parsuje request

Given klasa Admin\Support\TableListRequestFactory załadowana
When wywołam `TableListRequestFactory::fromRequest([], ['name', 'date_add'], 'date_add')` w smoke teście (z `$_GET = ['page'=>'2','per_page'=>'25','sort'=>'name']`)
Then otrzymam tablicę z `page=2`, `perPage=25`, `sortColumn='name'` (lub fallback do default jeśli nie jest w sortableColumns), oraz nieputą `viewFilters`/`queryFilters`

AC-3: FormRequestHandler i FormFieldRenderer mają poprawne use do 06-01

Given pliki utworzone w Admin\Support\Forms
When przeczytam pierwsze 10 linii FormRequestHandler.php i FormFieldRenderer.php
Then znajdę:
  - FormRequestHandler: `use Admin\ViewModels\Forms\FormEditViewModel;`, `use Admin\ViewModels\Forms\FormFieldType;`, `use Admin\Validation\FormValidator;`
  - FormFieldRenderer: `use Admin\ViewModels\Forms\FormEditViewModel;`, `use Admin\ViewModels\Forms\FormField;`, `use Admin\ViewModels\Forms\FormFieldType;`
And żaden plik nie ma `namespace admin\` (lowercase) ani `use admin\` (lowercase)

AC-4: Smoke test integracyjny — handler + renderer + Form VMs współpracują

Given wszystkie 3 nowe klasy + Form VMs z 06-01
When utworzę `FormField('email', FormFieldType::EMAIL, 'E-mail')`, opakowuję w `FormEditViewModel`, wywołuję `FormFieldRenderer::renderField($field, $vm)` w smoke teście
Then otrzymam string HTML zawierający `<input` i `name="email"`
And nie wystąpi fatal error o brakującej zależności

AC-5: Zero regresji — Phase 5 i 06-01 nadal działają

Given commit przed 06-02
When `php -r "require 'vendor/autoload.php'; foreach (['Domain\\Articles\\ArticlesRepository','Shared\\Helpers\\Helpers','Admin\\ViewModels\\Forms\\FormField','Admin\\Validation\\FormValidator'] as \$c) { var_dump(class_exists(\$c)); }"`
Then 4× `bool(true)` — żaden istniejący namespace nie został rozbity
And `git diff --stat autoload/admin/controls/ autoload/admin/factory/ autoload/admin/view/` pokazuje 0 zmian (legacy nietknięte)

</acceptance_criteria>

Task 1: Skopiuj TableListRequestFactory do Admin\Support z PSR-4 nazwą pliku autoload/admin/Support/TableListRequestFactory.php Utwórz katalog `autoload/admin/Support/` i skopiuj zawartość ze shopPRO: - Źródło: `C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\class.TableListRequestFactory.php` - Cel: `autoload/admin/Support/TableListRequestFactory.php` (BEZ `class.` prefiksu — PSR-4 wymaga match nazwa pliku = nazwa klasy)
Zmiana namespace: `namespace admin\Support;` → `namespace Admin\Support;` (PascalCase pierwszy segment, jak 06-01).

Cała reszta klasy (stałe `DEFAULT_PER_PAGE_OPTIONS`, `DEFAULT_PER_PAGE`, metoda statyczna `fromRequest()`, logika parsowania `$_GET` przez `\Shared\Helpers\Helpers::get()`) bez zmian.

Avoid:
- Pozostawienie prefiksu `class.` w nazwie pliku — psuje PSR-4
- Zmiany sygnatur metod (Phase 7+ zakłada API z shopPRO)
- Zmiany domyślnego sortColumn w sygnaturze
`php -l autoload/admin/Support/TableListRequestFactory.php` → "No syntax errors detected" `grep -c "^namespace Admin\\\\Support;" autoload/admin/Support/TableListRequestFactory.php` → 1 Po `composer dump-autoload`: `php -r "require 'vendor/autoload.php'; var_dump(class_exists('Admin\\\\Support\\\\TableListRequestFactory'));"` → `bool(true)` AC-1 częściowo (TableListRequestFactory ładuje się). AC-2 (smoke test factory) wykonywany w Task 3. Task 2: Skopiuj FormRequestHandler + FormFieldRenderer do Admin\Support\Forms\ autoload/admin/Support/Forms/FormRequestHandler.php, autoload/admin/Support/Forms/FormFieldRenderer.php Utwórz katalog `autoload/admin/Support/Forms/` i skopiuj 2 pliki ze shopPRO:
**FormRequestHandler.php** (159 L):
- Źródło: `C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormRequestHandler.php`
- Cel: `autoload/admin/Support/Forms/FormRequestHandler.php`
- Zmiany namespace/use:
  - `namespace admin\Support\Forms;` → `namespace Admin\Support\Forms;`
  - `use admin\ViewModels\Forms\FormEditViewModel;` → `use Admin\ViewModels\Forms\FormEditViewModel;`
  - `use admin\ViewModels\Forms\FormFieldType;` → `use Admin\ViewModels\Forms\FormFieldType;`
  - `use admin\Validation\FormValidator;` → `use Admin\Validation\FormValidator;`
- Cała reszta logiki (handle POST, persist do `$_SESSION`, redirect, walidacja przez `FormValidator`) bez zmian.

**FormFieldRenderer.php** (494 L):
- Źródło: `C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormFieldRenderer.php`
- Cel: `autoload/admin/Support/Forms/FormFieldRenderer.php`
- Zmiany namespace/use:
  - `namespace admin\Support\Forms;` → `namespace Admin\Support\Forms;`
  - `use admin\ViewModels\Forms\FormEditViewModel;` → `use Admin\ViewModels\Forms\FormEditViewModel;`
  - `use admin\ViewModels\Forms\FormField;` → `use Admin\ViewModels\Forms\FormField;`
  - `use admin\ViewModels\Forms\FormFieldType;` → `use Admin\ViewModels\Forms\FormFieldType;`
  - JEŻELI plik referuje również `FormTab` lub `FormAction` — analogicznie zaktualizuj `use`
- Cała reszta (metody `renderField`, `renderTabs`, `renderLangSection` itp.) bez zmian.

Po zapisaniu obu plików: `php composer.phar dump-autoload`.

Avoid:
- Refaktoringu generowanego HTML (UI templates Phase 7+ liczą na konkretny markup)
- Pozostawienia jakichkolwiek `admin\` (lowercase) referencji w obu plikach
- Zmiany w inkluzji innych klas spoza Admin\* (Shared\Html, Shared\Helpers)
`php -l autoload/admin/Support/Forms/FormRequestHandler.php` → "No syntax errors detected" `php -l autoload/admin/Support/Forms/FormFieldRenderer.php` → "No syntax errors detected" `grep -c "^namespace Admin\\\\Support\\\\Forms;" autoload/admin/Support/Forms/*.php` → 2 `grep -E "^use admin\\\\" autoload/admin/Support/Forms/*.php` → brak wyników (zero lowercase use) Po dump-autoload: `class_exists('Admin\\\\Support\\\\Forms\\\\FormRequestHandler')` i `class_exists('Admin\\\\Support\\\\Forms\\\\FormFieldRenderer')` → `bool(true)` × 2 AC-1 satisfied (wszystkie 3 klasy 06-02 ładują się), AC-3 satisfied (poprawne `use` Admin\... do 06-01). Task 3: Smoke test integracyjny — TableListRequestFactory + FormFieldRenderer z VM (brak modyfikacji — tylko runtime weryfikacja) Uruchom inline PHP smoke test sprawdzający 3 zachowania:
**A) TableListRequestFactory parsuje request:**
```
php -r "require 'vendor/autoload.php';
\$_GET = ['page'=>'2','per_page'=>'25','sort'=>'name','status'=>'1'];
\$ctx = Admin\Support\TableListRequestFactory::fromRequest(
  [['key'=>'status','label'=>'Status','type'=>'select','options'=>['1'=>'aktywny']]],
  ['name','date_add'],
  'date_add'
);
echo 'page='.\$ctx['page'].' perPage='.\$ctx['perPage'].' sort='.\$ctx['sortColumn'].PHP_EOL;
echo 'filters_status='.\$ctx['filters']['status'].PHP_EOL;
echo 'OK_FACTORY'.PHP_EOL;"
```
Oczekuję: `page=2 perPage=25 sort=name`, `filters_status=1`, `OK_FACTORY`.

**B) FormFieldRenderer renderuje pole:**
```
php -r "require 'vendor/autoload.php';
\$f = new Admin\ViewModels\Forms\FormField('email', Admin\ViewModels\Forms\FormFieldType::EMAIL, 'E-mail', 'a@b.c', 'main', true);
\$vm = new Admin\ViewModels\Forms\FormEditViewModel('test_form', 'Test', [], [\$f], [new Admin\ViewModels\Forms\FormTab('main','Main')]);
\$html = Admin\Support\Forms\FormFieldRenderer::renderField(\$f, \$vm);
echo 'HTML_HAS_INPUT='.(strpos(\$html,'<input')!==false?'YES':'NO').PHP_EOL;
echo 'HTML_HAS_NAME='.(strpos(\$html,'name=\"email\"')!==false?'YES':'NO').PHP_EOL;
echo 'OK_RENDERER'.PHP_EOL;"
```
Oczekuję: `HTML_HAS_INPUT=YES`, `HTML_HAS_NAME=YES`, `OK_RENDERER`.

Uwaga: Jeśli `renderField` jest niestatyczna lub ma inną sygnaturę niż wyżej zakłada — dostosuj wywołanie wedle faktycznej implementacji shopPRO (NIE modyfikuj klasy; popraw smoke test).

**C) FormRequestHandler ładuje się i ma referencję do FormValidator:**
```
php -r "require 'vendor/autoload.php';
\$h = new Admin\Support\Forms\FormRequestHandler();
echo get_class(\$h).PHP_EOL;
echo 'OK_HANDLER'.PHP_EOL;"
```
Oczekuję: `Admin\Support\Forms\FormRequestHandler`, `OK_HANDLER`.

**D) Zero regresji:**
```
php -r "require 'vendor/autoload.php';
foreach (['Domain\Articles\ArticlesRepository','Shared\Helpers\Helpers','Admin\ViewModels\Forms\FormField','Admin\Validation\FormValidator'] as \$c) {
  echo \$c.': '.(class_exists(\$c)?'OK':'FAIL').PHP_EOL;
}"
```
Oczekuję: 4× OK.

**Lint na nowych 3 plikach:**
`for f in autoload/admin/Support/TableListRequestFactory.php autoload/admin/Support/Forms/*.php; do php -l "$f"; done`

Avoid:
- Tworzenia trwałych plików testowych
- Modyfikacji shopPRO source files (read-only)
- Modyfikacji legacy `autoload/admin/controls/`, `factory/`, `view/`
Output zawiera: `OK_FACTORY`, `OK_RENDERER`, `OK_HANDLER`, oraz 4× `OK` dla zero-regression check `php -l` zielony × 3 dla nowych plików `git diff --stat autoload/admin/controls/ autoload/admin/factory/ autoload/admin/view/` → 0 zmian AC-2, AC-4, AC-5 satisfied (factory działa, renderer produkuje HTML, zero regresji).

DO NOT CHANGE

  • autoload/admin/controls/, factory/, view/ (legacy 50+ plików — boundaries Phase 19)
  • autoload/admin/ViewModels/, autoload/admin/Validation/ (utworzone w 06-01, stabilne)
  • autoload/Domain/**, autoload/Shared/** (Phase 1-5 closed)
  • autoload/autoloader.php
  • composer.json — TYLKO regen vendor/composer/autoload_* przez dump-autoload, BEZ edycji composer.json (pre 06-01 ustawienia są wystarczające)
  • composer.lock — bez composer update
  • shopPRO source files — read-only

SCOPE LIMITS

  • BaseController — explicit out-of-scope (decyzja: POJOs, brak BaseController)
  • Admin\App.php (logowanie, special_actions, routing) → Phase 12 (Users + 2FA)
  • Migracja istniejących admin\controls* na nowe wzorce — Phase 7+
  • Templates HTML wykorzystujące FormFieldRenderer — Phase 7+ (renderer już produkuje HTML, ale integracja z templates_admin/ to później)
  • Testy PHPUnit dla nowych klas — Phase 18
  • Refaktoring 494-liniowego FormFieldRenderer (np. split na mniejsze rendery per typ) — deferred
Przed declared complete: - [ ] `php -l` zielony dla 3 nowych plików - [ ] `composer dump-autoload` zwraca exit 0 bez warningów PSR-4 - [ ] Smoke test (Task 3) — wszystkie 4 sekcje (A/B/C/D) zwracają oczekiwane outputy - [ ] `grep -r "namespace admin\\\\Support" autoload/admin/Support/` → brak wyników (zero lowercase) - [ ] `git diff --stat` pokazuje TYLKO: 3 nowe pliki + regen vendor/composer/autoload_* - [ ] AC-1..AC-5 spełnione

<success_criteria>

  • 3 klasy Support dostępne: Admin\Support\TableListRequestFactory, Admin\Support\Forms\FormRequestHandler, Admin\Support\Forms\FormFieldRenderer
  • TableListRequestFactory poprawnie parsuje ?page=&per_page=&sort=&filter[]= z $_GET
  • FormFieldRenderer produkuje HTML pola formularza z FormField ViewModelu
  • Zero regresji: Domain, Shared, Admin\ViewModels, Admin\Validation działają jak przed 06-02
  • Phase 6 zamknięta — Phase 7+ ma cały fundament </success_criteria>
Po wykonaniu utworzyć `.paul/phases/06-admin-base/06-02-SUMMARY.md` z: - Lista skopiowanych plików (z liczbą linii) - Output 4 części smoke testu (literalnie) - Wszelkie deferred issues (np. refaktoring FormFieldRenderer 494 L) - Setup dla Phase 7 (jakie założenia: TableListRequestFactory + FormRequestHandler + FormFieldRenderer dostępne, brak BaseController — POJO pattern) - Aktualizacja `.paul/codebase/architecture.md` (deferred — robione w UNIFY)